Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
29 Oct 2025 · 6 min read ·Article 121 / 125
Go

121 Case Study: Product & Category GraphQL API with gqlgen

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

title: 121 Case Study: Product & Category GraphQL API with gqlgen
subtitle: Breaking Down the Architecture, Implementation, and Case Study of a Product-Categories API with gqlgen in Golang
author: insinyursoftware
date: 2024-06-22
tags: [graphql, golang, gqlgen, backend, studi-kasus, api]

GraphQL is becoming increasingly popular as a flexible API solution, especially for complex data-relationship scenarios. Among the many tools in the Go ecosystem, gqlgen stands out as one of the most solid frameworks for building GraphQL APIs in Golang.

In this article, I’ll share my experience building a simple API for products and categories, complete with a case study, query simulations, and a discussion of the code architecture using gqlgen. To make things clearer, I’ve included code samples, a simple database schema, and a visualization of the data-fetching flow between entities.


1. Case Study

Let’s imagine we’re building a simple e-commerce system with only two main entities:

  1. Product
    • Has an id, nama (name), harga (price), and kategori_id.
  2. Category
    • Has an id and nama (name).

Relationships between the entities:

  • A Product belongs to one Category.
  • A Category can have many Products.

We want to provide an API capable of:

  • Querying the list of products along with their category details.
  • Querying the list of categories along with their products.
  • Supporting filtering and relationships.

2. Database Structure

Here is the relational table layout we’ll use:

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

3. Designing the GraphQL Schema

We start by defining the GraphQL schema (schema.graphqls):

graphql
 1type Produk {
 2  id: ID!
 3  nama: String!
 4  harga: Int!
 5  kategori: Kategori!   # this is the Kategori relation (One-to-Many)
 6}
 7
 8type Kategori {
 9  id: ID!
10  nama: String!
11  produk: [Produk!]!   # this is the Produk relation (Many-to-One)
12}
13
14type Query {
15  produkList: [Produk!]!
16  kategoriList: [Kategori!]!
17  produkByKategori(kategoriId: ID!): [Produk!]!
18}

4. Project Structure

Make sure your project structure looks roughly like this:

text
 1.
 2├── gqlgen.yml
 3├── go.mod
 4├── main.go
 5├── graph
 6│   ├── model
 7│   │   └── models_gen.go
 8│   ├── resolver.go
 9│   ├── schema.graphqls
10│   ├── schema.resolvers.go
11│   └── database.go

5. Implementation with gqlgen

a. Generate the Code Skeleton

First, install it:

sh
1go get github.com/99designs/gqlgen
2go run github.com/99designs/gqlgen generate

After that, gqlgen will generate the boilerplate for the resolvers and models.


b. Creating a Mock Database

For this case study, a simple in-memory data store is enough.

go
 1// graph/database.go
 2
 3package graph
 4
 5import "graph/model"
 6
 7var kategoriData = []*model.Kategori{
 8    {ID: "1", Nama: "Elektronik"},
 9    {ID: "2", Nama: "Fashion"},
10}
11
12var produkData = []*model.Produk{
13    {ID: "1", Nama: "Laptop", Harga: 10000000, KategoriID: "1"},
14    {ID: "2", Nama: "HP", Harga: 4000000, KategoriID: "1"},
15    {ID: "3", Nama: "Celana Jeans", Harga: 250000, KategoriID: "2"},
16}

Add the KategoriID field to the model. If auto-generation doesn’t support it yet, edit the model directly:

go
1// graph/model/models_gen.go
2type Produk struct {
3    ID         string     `json:"id"`
4    Nama       string     `json:"nama"`
5    Harga      int        `json:"harga"`
6    KategoriID string     `json:"kategoriId"` // important for the relation!
7    Kategori   *Kategori  `json:"kategori"`
8}

c. Implementing the Resolvers

The produkList Query

go
 1// graph/schema.resolvers.go
 2
 3func (r *queryResolver) ProdukList(ctx context.Context) ([]*model.Produk, error) {
 4    return produkData, nil
 5}
 6
 7func (r *queryResolver) KategoriList(ctx context.Context) ([]*model.Kategori, error) {
 8    return kategoriData, nil
 9}
10
11func (r *queryResolver) ProdukByKategori(ctx context.Context, kategoriID string) ([]*model.Produk, error) {
12    var result []*model.Produk
13    for _, p := range produkData {
14        if p.KategoriID == kategoriID {
15            result = append(result, p)
16        }
17    }
18    return result, nil
19}

Relationship Field Resolvers

Because the Produk type in the schema has a kategori field, we need a custom resolver for it:

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

And the Kategori type has a produk field (many-to-one):

go
1func (r *kategoriResolver) Produk(ctx context.Context, obj *model.Kategori) ([]*model.Produk, error) {
2    var result []*model.Produk
3    for _, p := range produkData {
4        if p.KategoriID == obj.ID {
5            result = append(result, p)
6        }
7    }
8    return result, nil
9}

6. Query Simulation

Querying Products Along with Their Category

graphql
 1query {
 2  produkList {
 3    id
 4    nama
 5    harga
 6    kategori {
 7      id
 8      nama
 9    }
10  }
11}

Response:

json
 1{
 2  "data": {
 3    "produkList": [
 4      {
 5        "id": "1",
 6        "nama": "Laptop",
 7        "harga": 10000000,
 8        "kategori": {
 9          "id": "1",
10          "nama": "Elektronik"
11        }
12      },
13      ...
14    ]
15  }
16}

Querying Categories and Their Product Lists

graphql
 1query {
 2  kategoriList {
 3    id
 4    nama
 5    produk {
 6      id
 7      nama
 8      harga
 9    }
10  }
11}

Response:

json
 1{
 2  "data": {
 3    "kategoriList": [
 4      {
 5        "id": "1",
 6        "nama": "Elektronik",
 7        "produk": [
 8          { "id": "1", "nama": "Laptop", "harga": 10000000 },
 9          { "id": "2", "nama": "HP", "harga": 4000000 }
10        ]
11      },
12      // and so on
13    ]
14  }
15}

7. Product and Category Query Flow Diagram

It’s important to understand the resolver flow and how entities are resolved against one another. Here’s a simple mermaid diagram.

MERMAID
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. Product-Category Relational Table

ProdukKategori
LaptopElektronik
HPElektronik
Celana JeansFashion

9. Production Tips

  • For real data, swap the storage for a database (PostgreSQL, MySQL, etc.), then abstract the query logic inside the resolvers.
  • Use the DataLoader pattern so that field resolvers don’t suffer from N+1 (see the gqlgen dataloader ).
  • Error handling and input validation on mutations/queries should definitely be improved.
  • Separate the service and repository layers for scalability.

10. Conclusion

This product-category API case is a template you’ll find very frequently in real-world applications. With gqlgen, we can easily build the schema, resolvers, and data-relationship implementation in GraphQL. This short case study has demonstrated clean code structure, simple mock data, and even query simulations and data-flow visualization, all of which can be easily adapted to larger projects.

I hope this explanation opens up new insight into applying GraphQL in Golang for flexible and scalable relational data needs.


References


Bonus — Find the example code repo here (*if repo available)

Related Articles

💬 Comments