tutorial

121 Studi Kasus: API GraphQL Produk & Kategori dengan gqlgen


title: 121 Studi Kasus: API GraphQL Produk & Kategori dengan gqlgen
subtitle: Membongkar Arsitektur, Implementasi, dan Studi Kasus API Product-Categories dengan gqlgen di Golang
author: insinyursoftware
date: 2024-06-22
tags: [graphql, golang, gqlgen, backend, studi-kasus, api]

GraphQL semakin populer sebagai solusi API yang fleksibel, terutama untuk skenario relasi data yang kompleks. Di antara sekian banyak tools di ekosistem Go, gqlgen muncul sebagai salah satu framework paling solid untuk membangun GraphQL API di Golang.

Dalam artikel ini, saya akan membagikan pengalaman membangun API sederhana untuk produk dan kategori, lengkap dengan studi kasus, simulasi query, dan pembahasan arsitektur kode menggunakan gqlgen. Untuk pemahaman lebih baik, saya sertai contoh kode, skema database sederhana, dan visualisasi flow pengambilan data antar entitas.


1. Studi Kasus

Anggap saja kita membangun sebuah sistem e-commerce sederhana yang hanya memiliki dua entitas utama:

  1. Produk
    • Memiliki id, nama, harga, dan kategori_id.
  2. Kategori
    • Memiliki id dan nama.

Hubungan antar entitas:

  • Produk dimiliki oleh satu Kategori.
  • Kategori bisa memiliki banyak Produk.

Kita ingin menyediakan API yang mampu:

  • Query daftar produk beserta detail kategorinya.
  • Query daftar kategori beserta produk-produknya.
  • Mendukung filter dan relasi.

2. Struktur Database

Berikut ini tabel relasional yang digunakan:

produkkategori
id (int)id (int)
nama (string)nama (string)
harga (int)
kategori_id

3. Merancang Schema GraphQL

Kita mulai dari mendefinisikan schema GraphQL (schema.graphqls):

type Produk {
  id: ID!
  nama: String!
  harga: Int!
  kategori: Kategori!   # ini relasi Kategori (One-to-Many)
}

type Kategori {
  id: ID!
  nama: String!
  produk: [Produk!]!   # ini relasi Produk (Many-to-One)
}

type Query {
  produkList: [Produk!]!
  kategoriList: [Kategori!]!
  produkByKategori(kategoriId: ID!): [Produk!]!
}

4. Struktur Proyek

Pastikan struktur project kurang lebih seperti ini:

.
├── gqlgen.yml
├── go.mod
├── main.go
├── graph
│   ├── model
│   │   └── models_gen.go
│   ├── resolver.go
│   ├── schema.graphqls
│   ├── schema.resolvers.go
│   └── database.go

5. Implementasi dengan gqlgen

a. Generate Code Skeleton

Install dulu:

go get github.com/99designs/gqlgen
go run github.com/99designs/gqlgen generate

Setelah itu, gqlgen akan mengenerate boilerplate untuk resolver dan model.


b. Membuat Mock Database

Untuk studi kasus, kita cukup in-memory data store sederhana saja.

// graph/database.go

package graph

import "graph/model"

var kategoriData = []*model.Kategori{
    {ID: "1", Nama: "Elektronik"},
    {ID: "2", Nama: "Fashion"},
}

var produkData = []*model.Produk{
    {ID: "1", Nama: "Laptop", Harga: 10000000, KategoriID: "1"},
    {ID: "2", Nama: "HP", Harga: 4000000, KategoriID: "1"},
    {ID: "3", Nama: "Celana Jeans", Harga: 250000, KategoriID: "2"},
}

Tambahkan field KategoriID ke model. Jika auto-generation belum mendukung, edit langsung modelnya:

// graph/model/models_gen.go
type Produk struct {
    ID         string     `json:"id"`
    Nama       string     `json:"nama"`
    Harga      int        `json:"harga"`
    KategoriID string     `json:"kategoriId"` // penting untuk relasi!
    Kategori   *Kategori  `json:"kategori"`
}

c. Implementasi Resolver

Query produkList

// graph/schema.resolvers.go

func (r *queryResolver) ProdukList(ctx context.Context) ([]*model.Produk, error) {
    return produkData, nil
}

func (r *queryResolver) KategoriList(ctx context.Context) ([]*model.Kategori, error) {
    return kategoriData, nil
}

func (r *queryResolver) ProdukByKategori(ctx context.Context, kategoriID string) ([]*model.Produk, error) {
    var result []*model.Produk
    for _, p := range produkData {
        if p.KategoriID == kategoriID {
            result = append(result, p)
        }
    }
    return result, nil
}

Resolver Field Relasi

Karena di schema Produk punya field kategori, kita perlu resolver custom untuk ini:

func (r *produkResolver) Kategori(ctx context.Context, obj *model.Produk) (*model.Kategori, error) {
    for _, k := range kategoriData {
        if k.ID == obj.KategoriID {
            return k, nil
        }
    }
    return nil, errors.New("kategori not found")
}

Dan pada Kategori ada field produk (many-to-one):

func (r *kategoriResolver) Produk(ctx context.Context, obj *model.Kategori) ([]*model.Produk, error) {
    var result []*model.Produk
    for _, p := range produkData {
        if p.KategoriID == obj.ID {
            result = append(result, p)
        }
    }
    return result, nil
}

6. Simulasi Query

Query Produk beserta Kategori

query {
  produkList {
    id
    nama
    harga
    kategori {
      id
      nama
    }
  }
}

Response:

{
  "data": {
    "produkList": [
      {
        "id": "1",
        "nama": "Laptop",
        "harga": 10000000,
        "kategori": {
          "id": "1",
          "nama": "Elektronik"
        }
      },
      ...
    ]
  }
}

Query Kategori dan Daftar Produk-nya

query {
  kategoriList {
    id
    nama
    produk {
      id
      nama
      harga
    }
  }
}

Response:

{
  "data": {
    "kategoriList": [
      {
        "id": "1",
        "nama": "Elektronik",
        "produk": [
          { "id": "1", "nama": "Laptop", "harga": 10000000 },
          { "id": "2", "nama": "HP", "harga": 4000000 }
        ]
      },
      // dst
    ]
  }
}

7. Diagram Alur Query Produk dan Kategori

Penting untuk memahami flow resolver dan resolusi antar entitas. Berikut diagram mermaid sederhana.

graph TD
    A[Client] -- produkList query --> B[Query Resolver]
    B -- return array produk --> C[Produk Resolver]
    C -- resolve kategori field --> D[Kategori Resolver]
    D -- fetch Kategori dari produk.KategoriID --> E[KategoriData]

8. Tabel Relasional Produk-Kategori

ProdukKategori
LaptopElektronik
HPElektronik
Celana JeansFashion

9. Tips Production

  • Untuk data riil, ganti storage jadi database (PostgreSQL, MySQL, dsb), lalu abstract logic query di resolver.
  • Gunakan pattern DataLoader agar field resolver tidak N+1 (lihat gqlgen dataloader).
  • Error handling dan validasi input pada mutation/query wajib ditingkatkan.
  • Pisahkan layer service & repository untuk scalability.

10. Kesimpulan

Kasus API produk-kategori ini adalah template yang sangat sering ditemukan di aplikasi nyata. Dengan gqlgen, kita bisa dengan mudah menyusun schema, resolvers, dan implementasi relasi data di GraphQL. Studi kasus singkat ini telah memperlihatkan struktur kode rapi, mock data sederhana, hingga simulasi query dan visualisasi data flow, yang bisa dengan mudah diadaptasi ke project yang lebih besar.

Semoga penjelasan ini membuka insight baru tentang pengaplikasian GraphQL di Golang untuk kebutuhan relasional data yang fleksibel dan skalabel.


Referensi


Bonus — Temukan repo kode contoh di sini (*if repo available)

comments powered by Disqus