Belajar Fiber - Wrap-up: Proyek CRUD Lengkap
Episode 22 of 23

Belajar Fiber - Wrap-up: Proyek CRUD Lengkap

Episode terakhir seri ini menyatukan semuanya: membangun API CRUD lengkap dengan Fiber v3 — model dan store, routing group, binding dan validasi, custom error handler, middleware, serta uji dengan curl. Semua materi episode 0-21 dirangkai dalam satu proyek.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

Semua episode sebelumnya membahas potongan-potongan. Episode 22 — penutup seri ini — merangkainya menjadi proyek CRUD lengkap: aplikasi manajemen buku dengan Fiber v3 yang memakai routing, group, middleware, binding, validasi, dan error handling sekaligus.

Struktur Proyek

Pisahkan tanggung jawab ke file kecil agar mudah diuji:

  • main.go — perakitan aplikasi dan route
  • book.go — model beserta tag validasi
  • store.go — penyimpanan data in-memory
  • handler.go — handler CRUD dan error handler

Setup App dan Middleware

main.go menggabungkan konfigurasi, middleware, dan route:

main.go
func main() {
    app := fiber.New(fiber.Config{
        ErrorHandler: customErrorHandler,
        BodyLimit:    1 * 1024 * 1024,
    })
    app.Use(logger.New(), recover.New())
 
    books := app.Group("/books")
    books.Get("/", listBooks)
    books.Get("/:id<int>", getBook)
    books.Post("/", createBook)
    books.Put("/:id<int>", updateBook)
    books.Delete("/:id<int>", deleteBook)
 
    app.Listen(":3000")
}

ErrorHandler kustom (episode 9), middleware logger/recover (episode 6), dan constraint :id<int> (episode 4) dipasang sekaligus.

Model dan Validasi

book.go
type Book struct {
    ID     int    `json:"id"`
    Title  string `json:"title" validate:"required,min=1"`
    Author string `json:"author" validate:"required"`
    Year   int    `json:"year" validate:"gte=1900,lte=2100"`
}

Tag json untuk binding (episode 5); tag validate memastikan Title/Author wajib dan Year masuk rentang yang wajar.

Penyimpanan

store.go
type Store struct {
    mu    sync.RWMutex
    items map[int]Book
    next  int
}
 
func (s *Store) Create(b Book) Book {
    s.mu.Lock()
    defer s.mu.Unlock()
    s.next++
    b.ID = s.next
    s.items[b.ID] = b
    return b
}
func (s *Store) Get(id int) (Book, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    b, ok := s.items[id]
    return b, ok
}

sync.RWMutex melindungi map dari akses bersamaan karena handler Go berjalan paralel.

Handler CRUD

Handler memakai binding dan validasi otomatis:

handler.go: create dan list
var store = &Store{items: map[int]Book{}}
 
func createBook(c fiber.Ctx) error {
    var b Book
    if err := c.Bind().JSON(&b); err != nil {
        return err
    }
    return c.Status(fiber.StatusCreated).JSON(store.Create(b))
}
 
func listBooks(c fiber.Ctx) error {
    return c.JSON(store.All())
}

c.Bind().JSON(&b) mem-parse body dan menjalankan validasi; error diteruskan ke ErrorHandler.

handler.go: get, update, delete
func getBook(c fiber.Ctx) error {
    id, _ := strconv.Atoi(c.Params("id"))
    b, ok := store.Get(id)
    if !ok {
        return fiber.NewError(fiber.StatusNotFound, "buku tidak ditemukan")
    }
    return c.JSON(b)
}
 
func updateBook(c fiber.Ctx) error {
    id, _ := strconv.Atoi(c.Params("id"))
    var b Book
    if err := c.Bind().JSON(&b); err != nil {
        return err
    }
    updated, ok := store.Update(id, b)
    if !ok {
        return fiber.NewError(fiber.StatusNotFound, "buku tidak ditemukan")
    }
    return c.JSON(updated)
}
 
func deleteBook(c fiber.Ctx) error {
    id, _ := strconv.Atoi(c.Params("id"))
    if err := store.Delete(id); err != nil {
        return fiber.NewError(fiber.StatusNotFound, "buku tidak ditemukan")
    }
    return c.SendStatus(fiber.StatusNoContent)
}

getBook mengembalikan 404 lewat fiber.NewError saat id tidak ada; deleteBook membalas 204 setelah berhasil menghapus.

Error Handler

handler.go: error handler
func customErrorHandler(c fiber.Ctx, err error) error {
    code := fiber.StatusInternalServerError
    var fe *fiber.Error
    if errors.As(err, &fe) {
        code = fe.Code
    }
    return c.Status(code).JSON(fiber.Map{
        "error": fiber.Map{
            "code":    code,
            "message": err.Error(),
        },
    })
}

Uji Coba

Uji API CRUD
curl -X POST -H "Content-Type: application/json" \
  --data '{"title":"Clean Code","author":"Robert Martin","year":2008}' http://localhost:3000/books
curl http://localhost:3000/books
curl http://localhost:3000/books/1
curl -X PUT -H "Content-Type: application/json" --data '{"title":"Clean Code","author":"Robert C. Martin","year":2008}' http://localhost:3000/books/1
curl -X DELETE http://localhost:3000/books/1
curl http://localhost:3000/books/999

Penutup

Selamat — kalian telah menyelesaikan seri Belajar Fiber! Inti yang harus dibawa pulang:

  • Pisahkan model, store, handler, dan main ke file terpisah.
  • Group dan constraint (:id<int>) menjaga route tetap terorganisasi.
  • Binding + validasi otomatis membuat handler pendek dan aman.
  • Store berbasis mutex menangani akses bersamaan dengan benar.
  • fiber.NewError + custom ErrorHandler menyatukan format error.
  • Mulai proyek kalian sendiri — jalankan bun run dev dan uji dengan curl.