info-rest-api-gofiber-v3-crud-api

การพัฒนา Backend ในปัจจุบันมักต้องสร้าง REST API เพื่อให้ Frontend, Mobile Application หรือระบบอื่นสามารถเรียกใช้ข้อมูลและบริการจาก Server ได้ผ่าน HTTP

ภาษา Go (Golang) เป็นตัวเลือกที่ได้รับความนิยมสำหรับงาน Backend เพราะมีจุดเด่นด้านประสิทธิภาพ การใช้ทรัพยากรที่ค่อนข้างต่ำ การรองรับ Concurrent Programming และสามารถ Build เป็นไฟล์ Binary เพื่อนำไป Deploy ได้สะดวก

หนึ่งใน Web Framework ที่ได้รับความนิยมใน Go คือ Fiber ซึ่งมีรูปแบบการเขียน API ที่กระชับและได้รับแรงบันดาลใจจาก Express.js

บทความนี้จะสร้าง REST API แบบ CRUD ด้วย GoFiber v3 ตั้งแต่เริ่มต้นจนสามารถนำไปต่อยอดกับ PostgreSQL, JWT, Docker และ CI/CD ได้

บทความนี้อ้างอิง Fiber v3 ปัจจุบัน โดยเอกสารทางการระบุว่าต้องใช้ Go 1.25 ขึ้นไป และติดตั้ง Fiber ผ่านโมดูล github.com/gofiber/fiber/v3


#1. REST API คืออะไร

REST หรือ Representational State Transfer เป็นแนวทางออกแบบ Web API โดยใช้ HTTP Method เพื่อกำหนดการดำเนินการกับ Resource

ตัวอย่าง Resource ชื่อ products

HTTP Method Endpoint การทำงาน
GET /api/v1/products แสดงสินค้าทั้งหมด
GET /api/v1/products/:id แสดงสินค้าตาม ID
POST /api/v1/products เพิ่มสินค้า
PUT /api/v1/products/:id แก้ไขสินค้า
DELETE /api/v1/products/:id ลบสินค้า

แนวคิดพื้นฐานสามารถมองได้ว่า

Client
  |
  | HTTP Request
  v
REST API
  |
  +--> Router
  |
  +--> Handler
  |
  +--> Business Logic
  |
  +--> Repository / Database
  |
  v
JSON Response

#2. ทำไมเลือก GoFiber

Fiber เป็น Web Framework สำหรับ Go ที่สร้างอยู่บน fasthttp และออกแบบ API ให้ใช้งานง่ายในลักษณะที่นักพัฒนาที่เคยใช้ Express.js สามารถเรียนรู้ได้รวดเร็ว

จุดเด่นที่เหมาะกับการสร้าง REST API ได้แก่

  • Routing เขียนง่าย
  • รองรับ Middleware
  • จัดการ JSON Request/Response ได้สะดวก
  • รองรับ Route Group
  • มี Middleware สำหรับ Logger, CORS, Recover, Limiter และอื่น ๆ
  • เหมาะกับ Microservices และ Container
  • สามารถ Build เป็น Single Binary
  • ทำงานร่วมกับ PostgreSQL, MySQL, Redis และระบบ Cloud Native ได้ง่าย

#Workshop: Product REST API

ใน Workshop นี้เราจะสร้าง API สำหรับจัดการสินค้า โดยเริ่มจากการเก็บข้อมูลแบบ In-memory ก่อน เพื่อให้เข้าใจ Flow ของ Fiber ได้ง่าย

โครงสร้างข้อมูล Product

{
  "id": 1,
  "name": "Mechanical Keyboard",
  "price": 2590
}

#3. ตรวจสอบ Go

ตรวจสอบว่าเครื่องติดตั้ง Go แล้ว

go version

Fiber v3 ระบุให้ใช้ Go 1.25 ขึ้นไป

หากใช้ Go เวอร์ชันเก่าควรอัปเดตก่อนเริ่มโปรเจกต์


#4. สร้างโปรเจกต์

สร้าง Folder

mkdir go-fiber-rest-api
cd go-fiber-rest-api

สร้าง Go Module

go mod init example.com/go-fiber-rest-api

ติดตั้ง Fiber v3

go get github.com/gofiber/fiber/v3

ตรวจสอบไฟล์

go-fiber-rest-api/
├── go.mod
└── go.sum

#5. Hello World ด้วย GoFiber

สร้างไฟล์

main.go

เพิ่ม Code

package main

import (
	"log"

	"github.com/gofiber/fiber/v3"
)

func main() {
	app := fiber.New()

	app.Get("/", func(c fiber.Ctx) error {
		return c.JSON(fiber.Map{
			"message": "Hello GoFiber v3",
		})
	})

	log.Fatal(app.Listen(":3000"))
}

รัน

go run .

เปิด Browser หรือเรียก API

curl http://localhost:3000

ผลลัพธ์

{
  "message": "Hello GoFiber v3"
}

#6. สร้าง Product Model

ในตัวอย่างแรกจะเขียนทั้งหมดไว้ใน main.go เพื่อให้เห็น Flow ได้ชัดเจน

กำหนด Product

type Product struct {
	ID    int     `json:"id"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

และ Request สำหรับสร้างหรือแก้ไขข้อมูล

type ProductRequest struct {
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

สร้างข้อมูลเริ่มต้น

var products = []Product{
	{
		ID:    1,
		Name:  "Mechanical Keyboard",
		Price: 2590,
	},
	{
		ID:    2,
		Name:  "Gaming Mouse",
		Price: 1290,
	},
}

เพื่อให้ตัวอย่างปลอดภัยขึ้นเมื่อต้องรับ Request หลายรายการพร้อมกัน เราจะใช้ sync.RWMutex

var mu sync.RWMutex

#7. GET: ดึงข้อมูลสินค้าทั้งหมด

สร้าง Endpoint

GET /api/v1/products

Handler

func getProducts(c fiber.Ctx) error {
	mu.RLock()
	defer mu.RUnlock()

	return c.JSON(fiber.Map{
		"data": products,
	})
}

ผลลัพธ์ตัวอย่าง

{
  "data": [
    {
      "id": 1,
      "name": "Mechanical Keyboard",
      "price": 2590
    },
    {
      "id": 2,
      "name": "Gaming Mouse",
      "price": 1290
    }
  ]
}

#8. GET: ดึงข้อมูลสินค้าตาม ID

Endpoint

GET /api/v1/products/:id

ตัวอย่าง

GET /api/v1/products/1

Handler

func getProductByID(c fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))

	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid product id",
		})
	}

	mu.RLock()
	defer mu.RUnlock()

	for _, product := range products {
		if product.ID == id {
			return c.JSON(fiber.Map{
				"data": product,
			})
		}
	}

	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
		"error": "product not found",
	})
}

#9. POST: เพิ่มสินค้า

Endpoint

POST /api/v1/products

Request Body

{
  "name": "USB-C Hub",
  "price": 1490
}

Fiber v3 ใช้ Binding API ผ่าน

c.Bind().Body(...)

Handler

func createProduct(c fiber.Ctx) error {
	var req ProductRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid request body",
		})
	}

	if req.Name == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "name is required",
		})
	}

	if req.Price < 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "price must be greater than or equal to 0",
		})
	}

	mu.Lock()
	defer mu.Unlock()

	nextID := 1

	if len(products) > 0 {
		nextID = products[len(products)-1].ID + 1
	}

	product := Product{
		ID:    nextID,
		Name:  req.Name,
		Price: req.Price,
	}

	products = append(products, product)

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"data": product,
	})
}

เมื่อสร้างสำเร็จควรตอบ

201 Created

#10. PUT: แก้ไขสินค้า

Endpoint

PUT /api/v1/products/:id

ตัวอย่าง Request

{
  "name": "USB-C Hub 10-in-1",
  "price": 1890
}

Handler

func updateProduct(c fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))

	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid product id",
		})
	}

	var req ProductRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid request body",
		})
	}

	if req.Name == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "name is required",
		})
	}

	if req.Price < 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "price must be greater than or equal to 0",
		})
	}

	mu.Lock()
	defer mu.Unlock()

	for i := range products {
		if products[i].ID == id {
			products[i].Name = req.Name
			products[i].Price = req.Price

			return c.JSON(fiber.Map{
				"data": products[i],
			})
		}
	}

	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
		"error": "product not found",
	})
}

#11. DELETE: ลบสินค้า

Endpoint

DELETE /api/v1/products/:id

Handler

func deleteProduct(c fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))

	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid product id",
		})
	}

	mu.Lock()
	defer mu.Unlock()

	for i, product := range products {
		if product.ID == id {
			products = append(products[:i], products[i+1:]...)

			return c.SendStatus(fiber.StatusNoContent)
		}
	}

	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
		"error": "product not found",
	})
}

เมื่อสำเร็จ API ตอบ

204 No Content

#12. Route Group

แทนที่จะเขียน

app.Get("/api/v1/products", ...)
app.Get("/api/v1/products/:id", ...)
app.Post("/api/v1/products", ...)

สามารถสร้าง Route Group

api := app.Group("/api")
v1 := api.Group("/v1")
productRoutes := v1.Group("/products")

productRoutes.Get("/", getProducts)
productRoutes.Get("/:id", getProductByID)
productRoutes.Post("/", createProduct)
productRoutes.Put("/:id", updateProduct)
productRoutes.Delete("/:id", deleteProduct)

ช่วยให้รองรับ API Versioning ได้ง่าย เช่น

/api/v1/products
/api/v2/products

#13. Middleware

Middleware คือ Function ที่ทำงานระหว่าง Request และ Handler

ตัวอย่าง Flow

Request
   |
   v
Logger
   |
   v
CORS
   |
   v
Recover
   |
   v
Router
   |
   v
Handler
   |
   v
Response

ติดตั้งผ่าน Package ของ Fiber v3 ได้โดยตรง

import (
	"github.com/gofiber/fiber/v3/middleware/cors"
	"github.com/gofiber/fiber/v3/middleware/logger"
	recoverer "github.com/gofiber/fiber/v3/middleware/recover"
)

ใช้งาน

app.Use(logger.New())
app.Use(recoverer.New())
app.Use(cors.New())

#Logger

ใช้บันทึก HTTP Request/Response

app.Use(logger.New())

#Recover

ป้องกัน Panic ไม่ให้ Application Process จบลงจาก Handler โดยตรง และส่ง Error ไปยัง Error Handler ของ Fiber

app.Use(recoverer.New())

#CORS

ใช้ควบคุม Cross-Origin Request โดยเฉพาะเมื่อ Frontend และ Backend อยู่คนละ Origin

สำหรับ Production ไม่ควรปล่อยค่า CORS กว้างเกินความจำเป็น ควรกำหนด Origin ที่อนุญาตให้ชัดเจน


#14. Code ฉบับสมบูรณ์

ไฟล์ main.go

package main

import (
	"log"
	"strconv"
	"sync"

	"github.com/gofiber/fiber/v3"
	"github.com/gofiber/fiber/v3/middleware/cors"
	"github.com/gofiber/fiber/v3/middleware/logger"
	recoverer "github.com/gofiber/fiber/v3/middleware/recover"
)

type Product struct {
	ID    int     `json:"id"`
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

type ProductRequest struct {
	Name  string  `json:"name"`
	Price float64 `json:"price"`
}

var (
	products = []Product{
		{
			ID:    1,
			Name:  "Mechanical Keyboard",
			Price: 2590,
		},
		{
			ID:    2,
			Name:  "Gaming Mouse",
			Price: 1290,
		},
	}
	mu sync.RWMutex
)

func main() {
	app := fiber.New()

	app.Use(logger.New())
	app.Use(recoverer.New())
	app.Use(cors.New())

	app.Get("/", func(c fiber.Ctx) error {
		return c.JSON(fiber.Map{
			"message": "GoFiber REST API",
		})
	})

	app.Get("/health", func(c fiber.Ctx) error {
		return c.JSON(fiber.Map{
			"status": "ok",
		})
	})

	api := app.Group("/api")
	v1 := api.Group("/v1")
	productRoutes := v1.Group("/products")

	productRoutes.Get("/", getProducts)
	productRoutes.Get("/:id", getProductByID)
	productRoutes.Post("/", createProduct)
	productRoutes.Put("/:id", updateProduct)
	productRoutes.Delete("/:id", deleteProduct)

	log.Fatal(app.Listen(":3000"))
}

func getProducts(c fiber.Ctx) error {
	mu.RLock()
	defer mu.RUnlock()

	return c.JSON(fiber.Map{
		"data": products,
	})
}

func getProductByID(c fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))

	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid product id",
		})
	}

	mu.RLock()
	defer mu.RUnlock()

	for _, product := range products {
		if product.ID == id {
			return c.JSON(fiber.Map{
				"data": product,
			})
		}
	}

	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
		"error": "product not found",
	})
}

func createProduct(c fiber.Ctx) error {
	var req ProductRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid request body",
		})
	}

	if req.Name == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "name is required",
		})
	}

	if req.Price < 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "price must be greater than or equal to 0",
		})
	}

	mu.Lock()
	defer mu.Unlock()

	nextID := 1

	if len(products) > 0 {
		nextID = products[len(products)-1].ID + 1
	}

	product := Product{
		ID:    nextID,
		Name:  req.Name,
		Price: req.Price,
	}

	products = append(products, product)

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"data": product,
	})
}

func updateProduct(c fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))

	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid product id",
		})
	}

	var req ProductRequest

	if err := c.Bind().Body(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid request body",
		})
	}

	if req.Name == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "name is required",
		})
	}

	if req.Price < 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "price must be greater than or equal to 0",
		})
	}

	mu.Lock()
	defer mu.Unlock()

	for i := range products {
		if products[i].ID == id {
			products[i].Name = req.Name
			products[i].Price = req.Price

			return c.JSON(fiber.Map{
				"data": products[i],
			})
		}
	}

	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
		"error": "product not found",
	})
}

func deleteProduct(c fiber.Ctx) error {
	id, err := strconv.Atoi(c.Params("id"))

	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error": "invalid product id",
		})
	}

	mu.Lock()
	defer mu.Unlock()

	for i, product := range products {
		if product.ID == id {
			products = append(products[:i], products[i+1:]...)

			return c.SendStatus(fiber.StatusNoContent)
		}
	}

	return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
		"error": "product not found",
	})
}

#15. รัน API

จัด Format Code

go fmt ./...

ตรวจสอบ Dependency

go mod tidy

รัน

go run .

Server ทำงานที่

http://localhost:3000

#16. ทดสอบ API ด้วย curl

#Health Check

curl http://localhost:3000/health

#GET Products

curl http://localhost:3000/api/v1/products

#GET Product By ID

curl http://localhost:3000/api/v1/products/1

#POST Product

curl -X POST http://localhost:3000/api/v1/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "USB-C Hub",
    "price": 1490
  }'

#PUT Product

curl -X PUT http://localhost:3000/api/v1/products/1 \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mechanical Keyboard Pro",
    "price": 3290
  }'

#DELETE Product

curl -i -X DELETE \
  http://localhost:3000/api/v1/products/1

#17. HTTP Status Code ที่ควรรู้

Status ความหมาย ตัวอย่าง
200 OK Request สำเร็จ GET / PUT
201 Created สร้าง Resource สำเร็จ POST
204 No Content สำเร็จและไม่มี Response Body DELETE
400 Bad Request Request ไม่ถูกต้อง JSON หรือ ID ไม่ถูกต้อง
401 Unauthorized ยังไม่ได้ Authentication JWT ไม่ถูกต้อง
403 Forbidden ไม่มีสิทธิ์ User ไม่มี Role
404 Not Found ไม่พบ Resource Product ไม่มี
409 Conflict ข้อมูลขัดแย้ง Duplicate Data
422 Unprocessable Content Validation ไม่ผ่าน Field ไม่ถูกต้อง
500 Internal Server Error Server Error Database Error

#18. โครงสร้าง Response

ควรกำหนดรูปแบบ Response ให้สม่ำเสมอ

ตัวอย่าง Success

{
  "data": {
    "id": 1,
    "name": "Mechanical Keyboard",
    "price": 2590
  }
}

ตัวอย่าง Error

{
  "error": "product not found"
}

ในระบบขนาดใหญ่สามารถเพิ่ม

{
  "error": {
    "code": "PRODUCT_NOT_FOUND",
    "message": "product not found"
  }
}

เพื่อให้ Frontend จัดการ Error ได้ง่ายขึ้น


#19. แยกโครงสร้างโปรเจกต์

เมื่อ Application ใหญ่ขึ้น ไม่ควรเก็บทุกอย่างไว้ใน main.go

ตัวอย่างโครงสร้าง

go-fiber-rest-api/
│
├── cmd/
│   └── api/
│       └── main.go
│
├── internal/
│   ├── handler/
│   │   └── product_handler.go
│   │
│   ├── service/
│   │   └── product_service.go
│   │
│   ├── repository/
│   │   └── product_repository.go
│   │
│   ├── model/
│   │   └── product.go
│   │
│   ├── middleware/
│   └── routes/
│       └── routes.go
│
├── go.mod
├── go.sum
├── Dockerfile
└── compose.yaml

Flow

HTTP Request
     |
     v
   Router
     |
     v
   Handler
     |
     v
   Service
     |
     v
 Repository
     |
     v
  Database

ข้อดีของการแบ่ง Layer คือ

  • Code อ่านง่าย
  • Unit Test ง่าย
  • เปลี่ยน Database ได้สะดวก
  • Business Logic ไม่ผูกกับ HTTP Handler
  • รองรับ Dependency Injection ได้ดีขึ้น
  • เหมาะสำหรับระบบขนาดกลางและใหญ่

#20. Handler Layer

หน้าที่ของ Handler ควรเน้นเรื่อง HTTP เช่น

  • รับ Request
  • Bind JSON
  • ตรวจ Parameter เบื้องต้น
  • เรียก Service
  • กำหนด HTTP Status
  • ส่ง JSON Response

ตัวอย่าง

type ProductHandler struct {
	service ProductService
}

func (h *ProductHandler) Create(c fiber.Ctx) error {
	var req ProductRequest

	if err := c.Bind().Body(&req); err != nil {
		return fiber.ErrBadRequest
	}

	product, err := h.service.Create(req)

	if err != nil {
		return err
	}

	return c.Status(fiber.StatusCreated).JSON(product)
}

#21. Service Layer

Service ใช้เก็บ Business Logic

type ProductService interface {
	Create(req ProductRequest) (*Product, error)
	GetAll() ([]Product, error)
	GetByID(id int) (*Product, error)
	Update(id int, req ProductRequest) (*Product, error)
	Delete(id int) error
}

Service ไม่ควรสนใจรายละเอียดว่า Request มาจาก Fiber หรือ HTTP Framework ใด


#22. Repository Layer

Repository ใช้จัดการ Data Access

type ProductRepository interface {
	Create(product *Product) error
	FindAll() ([]Product, error)
	FindByID(id int) (*Product, error)
	Update(product *Product) error
	Delete(id int) error
}

Repository สามารถ Implement ด้วย

  • PostgreSQL
  • MySQL
  • MongoDB
  • SQLite
  • Redis
  • In-memory

ได้โดยไม่ต้องเปลี่ยน Business Logic มาก


#23. ต่อ PostgreSQL

Production API ควรเก็บข้อมูลใน Database จริง

Architecture

Client
   |
   v
GoFiber
   |
   v
Handler
   |
   v
Service
   |
   v
Repository
   |
   v
PostgreSQL

Library ที่สามารถใช้ได้ เช่น

database/sql
pgx
GORM
sqlc

ถ้าต้องการควบคุม SQL ชัดเจน pgx หรือ sqlc เป็นตัวเลือกที่น่าสนใจ

ถ้าต้องการ ORM และพัฒนา CRUD ได้รวดเร็ว สามารถใช้ GORM


#24. Authentication ด้วย JWT

เมื่อ API ต้องมี User Login สามารถเพิ่ม JWT

Flow ตัวอย่าง

POST /auth/login
       |
       v
ตรวจ Username / Password
       |
       v
สร้าง JWT
       |
       v
Client เก็บ Token
       |
       v
Authorization: Bearer <token>
       |
       v
JWT Middleware
       |
       v
Protected API

Endpoint เช่น

POST /api/v1/auth/login
GET  /api/v1/profile
GET  /api/v1/products
POST /api/v1/products

#25. Environment Variables

ไม่ควร Hard-code Configuration เช่น

password := "mypassword"

ควรใช้ Environment Variables

ตัวอย่าง .env

APP_PORT=3000
DB_HOST=postgres
DB_PORT=5432
DB_NAME=shop
DB_USER=app
DB_PASSWORD=change-me
JWT_SECRET=change-me

และต้องไม่ Commit .env ที่มี Secret ขึ้น Git Repository


#26. Dockerize GoFiber

ตัวอย่าง Multi-stage Dockerfile

FROM golang:1.25-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -o server .

FROM alpine:3.22

WORKDIR /app

COPY --from=builder /app/server ./server

EXPOSE 3000

CMD ["./server"]

Build

docker build -t go-fiber-api .

Run

docker run --rm \
  -p 3000:3000 \
  go-fiber-api

#27. Docker Compose

หากมี PostgreSQL สามารถใช้ Compose

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DB_HOST: postgres
      DB_PORT: 5432
      DB_NAME: shop
      DB_USER: app
      DB_PASSWORD: secret
    depends_on:
      - postgres

  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: shop
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Run

docker compose up --build

#28. Testing

REST API ควรมี Automated Test

สามารถทดสอบในระดับต่าง ๆ ได้ เช่น

Unit Test
   |
   v
Service Test
   |
   v
Handler Test
   |
   v
Integration Test
   |
   v
API / End-to-End Test

เครื่องมือที่ใช้ร่วมได้

  • Go testing
  • httptest
  • Postman
  • Newman
  • Postman CLI
  • Playwright API Testing
  • k6 สำหรับ Load Testing
  • JMeter สำหรับ Performance Testing

#29. แนวทาง API สำหรับ Production

ระบบจริงควรเพิ่มเติมอย่างน้อย

#Validation

ตรวจสอบข้อมูลก่อนบันทึก

name != ""
price >= 0

#Authentication

เช่น JWT หรือ OAuth 2.0

#Authorization

ตรวจ Role หรือ Permission

Admin
Staff
Customer

#Structured Logging

เก็บข้อมูลที่ช่วย Trace Request เช่น

request_id
method
path
status
latency

#Rate Limiting

ช่วยควบคุมจำนวน Request

#CORS Policy

กำหนด Origin ที่อนุญาตอย่างชัดเจน

#Timeout

กำหนด Timeout สำหรับ Database หรือ External Service

#Graceful Shutdown

หยุด Server อย่างถูกต้องเมื่อ Deploy หรือ Restart Container

#Observability

เพิ่ม

Metrics
Logs
Traces

แล้วเชื่อมกับระบบ เช่น

Prometheus
Grafana
OpenTelemetry
Loki

#30. REST API Architecture ที่เหมาะกับระบบจริง

แนวทางหนึ่งที่สามารถใช้ได้

                 ┌───────────────┐
                 │    Client     │
                 └───────┬───────┘
                         │
                         ▼
                ┌─────────────────┐
                │    GoFiber      │
                │     Router      │
                └────────┬────────┘
                         │
          ┌──────────────▼──────────────┐
          │        Middleware           │
          │ Logger / CORS / JWT / Rate │
          └──────────────┬──────────────┘
                         │
                         ▼
                 ┌───────────────┐
                 │    Handler    │
                 └───────┬───────┘
                         │
                         ▼
                 ┌───────────────┐
                 │    Service    │
                 └───────┬───────┘
                         │
                         ▼
                 ┌───────────────┐
                 │  Repository   │
                 └───────┬───────┘
                         │
                         ▼
                 ┌───────────────┐
                 │  PostgreSQL   │
                 └───────────────┘

#31. Roadmap การเรียนรู้ต่อ

หลังจากสร้าง CRUD API สำเร็จ สามารถพัฒนาต่อเป็นลำดับดังนี้

Go Fundamentals
      ↓
GoFiber
      ↓
REST API
      ↓
CRUD
      ↓
Validation
      ↓
PostgreSQL
      ↓
Repository Pattern
      ↓
JWT Authentication
      ↓
Unit / Integration Testing
      ↓
Docker
      ↓
CI/CD
      ↓
Kubernetes
      ↓
Observability

#32. สรุป

GoFiber ช่วยให้การพัฒนา REST API ด้วย Go ทำได้รวดเร็วและ Code มีรูปแบบที่กระชับ โดยเฉพาะสำหรับผู้ที่คุ้นเคยกับ Express.js

Workshop นี้ครอบคลุมพื้นฐานสำคัญ ได้แก่

Routing
CRUD
JSON Binding
Validation
HTTP Status Code
Middleware
Route Group
Error Response
Project Structure
Docker

สำหรับระบบ Production ควรต่อยอดไปสู่ Architecture แบบแยก Layer

Handler
   ↓
Service
   ↓
Repository
   ↓
Database

จากนั้นเพิ่มองค์ประกอบสำคัญ เช่น

PostgreSQL
JWT
Validation
Testing
Docker
CI/CD
Kubernetes
Observability

แนวทางนี้ทำให้ REST API ที่เริ่มต้นจากโปรเจกต์เล็กสามารถขยายไปเป็น Backend Service หรือ Microservice ที่ดูแลรักษาได้ง่ายขึ้นในอนาคต


#References