tutorial

23 Modularisasi File Resolver dan Skema

23 Modularisasi File Resolver dan Skema: Pendekatan Terstruktur Membangun GraphQL API

Membangun API GraphQL seringkali membawa kita pada masalah-masalah modularitas terhadap skema dan resolver. Jika dibiarkan tumbuh secara monolitik, skema dan resolver mudah menjadi susah dibaca, rawan konflik import, dan berisiko besar ketika di-maintain engineer lain — atau bahkan kita sendiri, di masa depan.

Pada artikel kali ini, saya akan membahas teknik modularisasi file resolver dan skema dalam proyek GraphQL NodeJS menggunakan apollo-server, serta mengilustrasikan praktik terbaik dengan contoh kode, simulasi skenario, hingga diagram modularisasi menggunakan mermaid. Semua dengan gaya sederhana namun profesional, sebagaimana kita para engineer saling berbagi insight di Medium.


Mengapa Butuh Modularisasi?

Mari kita mulai dari permasalahan umum — biasanya, dalam fase awal, kita mendefinisikan seluruh skema dan resolver dalam satu file, misalnya schema.js dan resolvers.js, seperti ini:

// schema.js
const { gql } = require('apollo-server');

const typeDefs = gql`
  type Query {
    books: [Book]
  }
  type Book {
    title: String
    author: String
  }
`;

module.exports = typeDefs;

// resolvers.js
const resolvers = {
  Query: {
    books: () => [{ title: "1984", author: "Orwell" }]
  }
};

module.exports = resolvers;

Ini baik untuk prototipe atau skala sangat kecil. Tapi dengan bertambahnya resource (misalnya entities baru seperti User, Post, Comment), file tersebut makin membengkak. Semua hal numpuk dalam satu file, kolaborasi jadi sulit, testing jadi repot, risiko conflict merge berlangsung tinggi.

Rangkuman Tantangan Tanpa Modularisasi

MasalahEfek Negatif
File menumpukSulit navigasi & refactor
Naming conflictError tidak terdeteksi sebelum runtime
Sulit diujiTeardown & setup test jadi rumit
Merge conflictSering tabrakan saat kolaborasi

Prinsip Modularisasi Resolver & Skema

Goal: Setiap entity (domain) memiliki file skema dan resolver sendiri, lalu di-merge otomatis. Mirip konsep “feature folder” pada frontend (misal React).

Contohnya, kita punya struktur folder seperti ini:

src/
 ├── graphql/
 |    ├── book/
 |    |    ├── typeDefs.js
 |    |    └── resolvers.js
 |    ├── user/
 |    |    ├── typeDefs.js
 |    |    └── resolvers.js
 |    └── index.js
 └── server.js

Benefit:

  • Mudah scale up/down fitur
  • Tim lain bisa kolaborasi tanpa geser file utama
  • Test per domain/entity
  • Maintainable & readable

Implementasi Modular GraphQL

Mari kita bahas step-by-step cara modularisasi file skema dan resolver.

1. Definisikan Schema dan Resolver per Domain

1.1. Book Module

// src/graphql/book/typeDefs.js
const { gql } = require('apollo-server');

const bookTypeDefs = gql`
  type Book {
    id: ID!
    title: String!
    author: String!
  }
  extend type Query {
    books: [Book!]!
    bookById(id: ID!): Book
  }
`;

module.exports = bookTypeDefs;

// src/graphql/book/resolvers.js
const bookResolvers = {
  Query: {
    books: () => [...], // bisa diisi dummy/mock data
    bookById: (_, { id }) => {...}
  }
};

module.exports = bookResolvers;

1.2. User Module

// src/graphql/user/typeDefs.js
const { gql } = require('apollo-server');

const userTypeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
  }
  extend type Query {
    users: [User!]!
    userById(id: ID!): User
  }
`;

module.exports = userTypeDefs;

// src/graphql/user/resolvers.js
const userResolvers = {
  Query: {
    users: () => [...],
    userById: (_, { id }) => {...}
  }
};

module.exports = userResolvers;

2. Root Skema (“Stitching”)

Kita butuh satu “root” skema dan resolver, biasanya di graphql/index.js:

// src/graphql/index.js
const { gql } = require('apollo-server');
const { mergeTypeDefs, mergeResolvers } = require('@graphql-tools/merge');

const bookTypeDefs = require('./book/typeDefs');
const userTypeDefs = require('./user/typeDefs');
const bookResolvers = require('./book/resolvers');
const userResolvers = require('./user/resolvers');

// Root type Query (perlu jika menggunakan extend type di module)
const rootTypeDefs = gql`
  type Query
`;

const typeDefs = mergeTypeDefs([
  rootTypeDefs, bookTypeDefs, userTypeDefs
]);
const resolvers = mergeResolvers([
  bookResolvers, userResolvers
]);

module.exports = { typeDefs, resolvers };

Note: Package @graphql-tools/merge sangat membantu stitch schema/resolver dengan clean.

3. Setup Server

// src/server.js
const { ApolloServer } = require('apollo-server');
const { typeDefs, resolvers } = require('./graphql');

const server = new ApolloServer({
  typeDefs,
  resolvers
});

server.listen().then(({ url }) => {
  console.log(`🚀  Server ready at ${url}`);
});

Simulasi Skenario Penambahan Modul

Bagaimana jika product owner minta menambah fitur baru, misalnya “Comments”?
Cukup tambahkan folder baru:

src/graphql/comment/
 ├── typeDefs.js
 └── resolvers.js

Lalu, import ke src/graphql/index.js:

// ...existing code ...
const commentTypeDefs = require('./comment/typeDefs');
const commentResolvers = require('./comment/resolvers');

const typeDefs = mergeTypeDefs([
  rootTypeDefs, bookTypeDefs, userTypeDefs, commentTypeDefs
]);
const resolvers = mergeResolvers([
  bookResolvers, userResolvers, commentResolvers
]);

Done! Tanpa otak-atik file lain.


Ilustrasi Modular Initiation Flow

Mari visualisasikan arsitektur modularisasi ini dengan mermaid diagram.

graph TD
    A[Server.js] --> B[graphql/index.js]
    B --> C[book/typeDefs.js & resolvers.js]
    B --> D[user/typeDefs.js & resolvers.js]
    B --> E[comment/typeDefs.js & resolvers.js]

Tabel Perbandingan Sebelum vs Sesudah Modularisasi

AspekMonolitikModular
Penambahan fiturSulit, rawan conflictSangat mudah
TeamworkSering merge crashFile terpisah, aman
TestingE2E dominanBisa unit per module
MaintainabilityMenurun seiring waktuMinimal changes
ScalabilityLemahMudah bertambah

Tips Modularisasi Resolver & Schema

  1. Gunakan folder per domain
    • Mudah tracking, scale, dan debug masalah.
  2. Sediakan folder root untuk glue/stitching
    • Misal /graphql/index.js.
  3. Jangan lupa root type Query
    • Kalau setiap typeDefs pakai extend type Query, perlu root type Query untuk initial schema.
  4. Pisahkan antara schema & resolver
    • Uji dan refactor lebih fleksibel.
  5. Gunakan tools merge
    • Integrasi skema/resolver jangan hardcode, pakai helper lib.
  6. Tambahkan test per module
    • Unit test tiap resolver & schema.

Penutup

Dengan modularisasi schema dan resolver, GraphQL API kita jauh lebih maintainable, scalable, dan nyaman untuk kolaborasi. Teknik ini bukan hanya best practice “kekinian”, tapi sudah terbukti jadi fundamental di berbagai codebase GraphQL production berskala besar.

Dengan ilustrasi kode, diagram, dan simulasi kasus di atas, saya harap Anda makin mantap membangun GraphQL yang siap scale-up secara profesional. Bagikan artikel ini jika menurut Anda bermanfaat — dan jangan ragu diskusi di kolom komentar, siapa tahu kita bisa saling belajar untuk modulasi berikutnya!

Happy coding, dan salam modular! 🚀


Resources:

comments powered by Disqus