Database

GraphQL untuk Pemula

Tutorial lengkap belajar GraphQL dari nol — perbandingan dengan REST, mendefinisikan schema, queries, mutations, subscriptions, dan implementasi dengan Apollo Server

1. Pengenalan GraphQL

GraphQL adalah bahasa query dan runtime untuk API yang dikembangkan oleh Meta (Facebook) pada tahun 2012 dan dirilis sebagai open-source pada tahun 2015. GraphQL memungkinkan klien meminta data yang spesifik sesuai kebutuhan — tidak lebih, tidak kurang.

Berbeda dengan REST yang menggunakan banyak endpoint, GraphQL menggunakan satu endpoint untuk semua operasi. Klien mengirim query yang mendeskripsikan data apa yang dibutuhkan, dan server mengembalikan response dengan struktur yang persis sesuai query tersebut.

Konsep Inti GraphQL

Konsep Penjelasan
SchemaKontrak antara client dan server yang mendefinisikan tipe data dan operasi yang tersedia
QueryOperasi untuk mengambil data (setara dengan GET di REST)
MutationOperasi untuk mengubah data (setara dengan POST/PUT/DELETE di REST)
SubscriptionOperasi untuk data realtime via WebSocket
ResolverFungsi yang menangani setiap field dalam schema — berisi logika bisnis
Type SystemSistem tipe yang kuat untuk memvalidasi query secara otomatis

Mengapa GraphQL?

Keunggulan Penjelasan
Tidak Ada Over-fetchingKlien hanya meminta data yang dibutuhkan — menghemat bandwidth
Tidak Ada Under-fetchingSatu query bisa mengambil data dari beberapa resource sekaligus
Strongly TypedSchema menjamin data yang dikembalikan selalu sesuai kontrak
IntrospectionClient bisa menanyakan schema untuk mengetahui data apa saja yang tersedia
ToolingGraphQL Playground, Apollo DevTools, dan code generation
Ekosistem BesarApollo, Relay, urql, GraphQL Code Generator, dan banyak lagi
Diagram: Arsitektur GraphQL
┌─────────────────────────────────────────────────────────┐
│                    CLIENT                                │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │              GraphQL Query                        │  │
│  │                                                  │  │
│  │  query {                                         │  │
│  │    user(id: 1) {                                 │  │
│  │      nama                                        │  │
│  │      email                                       │  │
│  │      posts { judul }                             │  │
│  │    }                                             │  │
│  │  }                                               │  │
│  └───────────────────────┬──────────────────────────┘  │
│                          │  HTTP POST                   │
└──────────────────────────┼──────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────┐
│                  GRAPHQL SERVER                          │
│                                                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │  Schema Definition (SDL)                          │  │
│  │  → Validasi query terhadap schema                 │  │
│  └───────────────────────┬───────────────────────────┘  │
│                          │                               │
│  ┌───────────────────────▼───────────────────────────┐  │
│  │  Resolvers                                        │  │
│  │  → user resolver → ambil dari database            │  │
│  │  → posts resolver → ambil relasi posts            │  │
│  └───────────────────────┬───────────────────────────┘  │
│                          │                               │
│  ┌───────────────────────▼───────────────────────────┐  │
│  │  Response (JSON)                                  │  │
│  │  { "data": { "user": { "nama": "Budi", ... } } } │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘
💡 Tips

GraphQL bukan pengganti database — ia adalah lapisan API yang berada di atas database (atau sumber data apa pun). GraphQL bisa mengambil data dari database SQL, NoSQL, REST API, microservices, atau bahkan file lokal.

2. GraphQL vs REST

Untuk memahami mengapa GraphQL lahir, kita perlu memahami keterbatasan REST dan bagaimana GraphQL mengatasinya. Mari kita bandungkan keduanya dengan contoh kasus nyata.

Contoh Kasus: Mengambil Data Pengguna dan Post

Aspek REST GraphQL
EndpointGET /api/users/1POST /graphql
Jumlah Request2-3 request (user + posts + comments)1 request saja
Over-fetchingMengembalikan semua field userHanya field yang diminta
Under-fetchingPerlu request terpisah untuk postsInclude posts dalam satu query
DokumentasiSwagger / OpenAPI (manual)Introspection (otomatis)
Versioning/api/v1/users, /api/v2/usersTidak perlu versioning

REST: Multiple Requests

REST — Beberapa Request
// REST: Perlu 3 request terpisah untuk mendapatkan semua data

// Request 1: Ambil data user
const user = await fetch('/api/users/1');
// Response: { id: 1, nama: "Budi", email: "budi@mail.com", umur: 25, alamat: "...", ... }

// Request 2: Ambil post user
const posts = await fetch('/api/users/1/posts');
// Response: [{ id: 1, judul: "Post 1", konten: "...", ... }, ...]

// Request 3: Ambil komentar setiap post
const comments = await fetch('/api/posts/1/comments');
// Response: [{ id: 1, teks: "Bagus!", ... }, ...]

// Masalah:
// - Over-fetching: user response punya field yang tidak dibutuhkan
// - Under-fetching: butuh request terpisah untuk posts dan comments
// - Waterfall: request 2 menunggu request 1 selesai

GraphQL: Satu Request

GraphQL — Single Query
# GraphQL: Semua data dalam satu query
query {
  user(id: 1) {
    nama
    email
    posts {
      judul
      published
      comments {
        teks
        author {
          nama
        }
      }
    }
  }
}

# Response (JSON):
# {
#   "data": {
#     "user": {
#       "nama": "Budi",
#       "email": "budi@mail.com",
#       "posts": [
#         {
#           "judul": "Post Pertama",
#           "published": true,
#           "comments": [
#             {
#               "teks": "Artikel bagus!",
#               "author": { "nama": "Sari" }
#             }
#           ]
#         }
#       ]
#     }
#   }
# }
⚠️ Kapan Tidak Menggunakan GraphQL?

GraphQL tidak selalu lebih baik dari REST. Gunakan REST untuk API sederhana dengan struktur data yang tidak berubah, file upload yang kompleks, atau caching HTTP yang kuat. GraphQL ideal untuk API yang digunakan oleh banyak client (web, mobile, IoT) dengan kebutuhan data yang berbeda-beda.

3. Mendefinisikan Schema

Schema adalah jantung dari API GraphQL. Schema mendefinisikan tipe data apa yang tersedia, field apa yang dimiliki setiap tipe, dan operasi (query, mutation, subscription) apa yang bisa dilakukan. Schema ditulis menggunakan Schema Definition Language (SDL).

Scalar Types (Tipe Dasar)

Tipe Deskripsi Contoh
IntInteger 32-bit25, -10, 0
FloatDesimal floating-point3.14, -0.5
StringTeks UTF-8"Hello", "Budi"
BooleanBenar atau salahtrue, false
IDIdentifier unik (serialized sebagai String)"abc123"

Object Types dan Schema

GraphQL — Schema Definition
# Mendefinisikan tipe data User
type User {
  id: ID!
  nama: String!
  email: String!
  umur: Int
  isActive: Boolean!
  posts: [Post!]!
  profile: Profile
  createdAt: String!
}

# Tanda ! berarti field wajib (non-nullable)
# [Post!]! berarti array yang tidak null dan elemennya tidak null

type Post {
  id: ID!
  judul: String!
  konten: String
  published: Boolean!
  author: User!
  comments: [Comment!]!
  tags: [Tag!]!
  createdAt: String!
}

type Comment {
  id: ID!
  teks: String!
  author: User!
  post: Post!
}

type Tag {
  id: ID!
  nama: String!
  posts: [Post!]!
}

type Profile {
  id: ID!
  bio: String
  avatar: String
  user: User!
}

# Root types — mendefinisikan operasi yang tersedia
type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
  post(id: ID!): Post
  posts(published: Boolean, tag: String): [Post!]!
  searchPosts(keyword: String!): [Post!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
  createPost(input: CreatePostInput!): Post!
  publishPost(id: ID!): Post!
}

type Subscription {
  postCreated: Post!
  commentAdded(postId: ID!): Comment!
}

Input Types

GraphQL — Input Types
# Input types digunakan sebagai argumen untuk mutations
# Input type mirip dengan object type, tapi khusus untuk input

input CreateUserInput {
  nama: String!
  email: String!
  umur: Int
}

input UpdateUserInput {
  nama: String
  email: String
  umur: Int
}

input CreatePostInput {
  judul: String!
  konten: String
  authorId: ID!
  tagIds: [ID!]
}

# Enum — tipe dengan nilai yang terbatas
enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

enum SortOrder {
  ASC
  DESC
}

# Interface — kontrak yang harus dipenuhi oleh tipe
interface Node {
  id: ID!
}

# Union — gabungan beberapa tipe
union SearchResult = User | Post | Comment
💡 Tips

Gunakan input types untuk argumen mutation agar schema lebih rapi dan reusable. Jangan mengirim banyak argumen scalar — kemas dalam satu input type. Ini juga memudahkan validasi di sisi server.

4. Queries: Mengambil Data

Query adalah operasi untuk mengambil data dari server GraphQL. Query memiliki struktur yang sama dengan response JSON yang diharapkan — ini memudahkan developer memahami data apa yang akan diterima.

Query Dasar

GraphQL — Query
# Query sederhana
query {
  users {
    id
    nama
    email
  }
}

# Query dengan argumen
query {
  user(id: "1") {
    nama
    email
    umur
    profile {
      bio
      avatar
    }
  }
}

# Query dengan variabel
query GetUser($userId: ID!) {
  user(id: $userId) {
    nama
    email
    posts {
      judul
      published
    }
  }
}

# Variabel dikirim terpisah:
# { "userId": "1" }

# Query dengan directive
query GetUser($withPosts: Boolean!) {
  user(id: "1") {
    nama
    email
    posts @include(if: $withPosts) {
      judul
      published
    }
  }
}

# Fragment — reusable bagian query
fragment UserBasicInfo on User {
  id
  nama
  email
  isActive
}

fragment PostDetail on Post {
  id
  judul
  konten
  published
  createdAt
}

query GetDashboard {
  users(limit: 10) {
    ...UserBasicInfo
  }
  recentPosts: posts(published: true) {
    ...PostDetail
    author {
      nama
    }
  }
}

# Aliases — mengquery tipe yang sama dengan argumen berbeda
query {
  activeUsers: users(limit: 5) {
    nama
  }
  inactiveUsers: users(limit: 5) {
    nama
  }
}

Nested Queries dan Relasi

GraphQL — Nested Queries
# Mengambil data dengan relasi bertingkat
query {
  user(id: "1") {
    nama
    posts {
      judul
      comments {
        teks
        author {
          nama
          profile {
            avatar
          }
        }
      }
      tags {
        nama
      }
    }
  }
}

# Response yang dihasilkan:
# {
#   "data": {
#     "user": {
#       "nama": "Budi",
#       "posts": [
#         {
#           "judul": "Belajar GraphQL",
#           "comments": [
#             {
#               "teks": "Mantap!",
#               "author": {
#                 "nama": "Sari",
#                 "profile": {
#                   "avatar": "https://..."
#                 }
#               }
#             }
#           ],
#           "tags": [
#             { "nama": "GraphQL" },
#             { "nama": "API" }
#           ]
#         }
#       ]
#     }
#   }
# }

5. Mutations: Mengubah Data

Mutation adalah operasi untuk membuat, memperbarui, atau menghapus data. Berbeda dengan query yang bisa dijalankan secara paralel, mutation dieksekusi secara berurutan untuk menjaga konsistensi data.

GraphQL — Mutations
# Membuat user baru
mutation {
  createUser(input: {
    nama: "Budi Santoso"
    email: "budi@example.com"
    umur: 25
  }) {
    id
    nama
    email
    createdAt
  }
}

# Mengupdate user
mutation {
  updateUser(
    id: "1"
    input: {
      nama: "Budi S. Updated"
      umur: 26
    }
  ) {
    id
    nama
    umur
  }
}

# Menghapus user
mutation {
  deleteUser(id: "5")
}

# Mutation dengan variabel
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    judul
    author {
      nama
    }
  }
}

# Variabel:
# {
#   "input": {
#     "judul": "Post Baru",
#     "konten": "Ini konten post...",
#     "authorId": "1",
#     "tagIds": ["1", "2"]
#   }
# }

# Multiple mutations dalam satu request
mutation {
  post1: createPost(input: { judul: "Post 1", authorId: "1" }) {
    id
    judul
  }
  post2: createPost(input: { judul: "Post 2", authorId: "1" }) {
    id
    judul
  }
}

Error Handling di Mutation

GraphQL — Error Pattern
# Pola error handling dengan Union type
union CreateUserResult = User | ValidationError

type ValidationError {
  field: String!
  message: String!
}

mutation {
  createUser(input: { nama: "", email: "invalid" }) {
    ... on User {
      id
      nama
      email
    }
    ... on ValidationError {
      field
      message
    }
  }
}

# Response jika ada error:
# {
#   "data": {
#     "createUser": {
#       "__typename": "ValidationError",
#       "field": "nama",
#       "message": "Nama tidak boleh kosong"
#     }
#   }
# }

6. Subscriptions: Data Realtime

Subscription memungkinkan client menerima data secara realtime ketika ada perubahan di server. Subscription menggunakan WebSocket sebagai transport protocol, bukan HTTP biasa.

GraphQL — Subscription
# Schema untuk subscription
type Subscription {
  # Triggered ketika post baru dibuat
  postCreated: Post!

  # Triggered ketika komentar ditambahkan ke post tertentu
  commentAdded(postId: ID!): Comment!

  # Triggered ketika user online status berubah
  userStatusChanged: User!
}

# Client mengirim subscription:
subscription OnNewPost {
  postCreated {
    id
    judul
    author {
      nama
    }
    createdAt
  }
}

# Client mengirim subscription untuk komentar spesifik:
subscription OnNewComment($postId: ID!) {
  commentAdded(postId: $postId) {
    id
    teks
    author {
      nama
    }
    createdAt
  }
}

Contoh Implementasi Resolver Subscription

JavaScript — Subscription Resolver
// Menggunakan PubSub untuk mengelola event
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

const resolvers = {
  Subscription: {
    postCreated: {
      subscribe: () => pubsub.asyncIterator(['POST_CREATED']),
    },
    commentAdded: {
      subscribe: (_, { postId }) => {
        return pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]);
      },
    },
  },

  Mutation: {
    createPost: async (_, { input }, context) => {
      const post = await context.db.post.create({ data: input });

      // Publish event ke semua subscriber
      pubsub.publish('POST_CREATED', {
        postCreated: post,
      });

      return post;
    },

    addComment: async (_, { postId, teks }, context) => {
      const comment = await context.db.comment.create({
        data: { teks, postId, authorId: context.user.id },
      });

      pubsub.publish(`COMMENT_ADDED_${postId}`, {
        commentAdded: comment,
      });

      return comment;
    },
  },
};
⚠️ Catatan Penting Subscription

Subscription membutuhkan WebSocket server yang terpisah dari HTTP server. Di production, pastikan menggunakan library seperti graphql-ws (bukan subscriptions-transport-ws yang sudah deprecated). Perhatikan juga limit koneksi WebSocket agar server tidak overload.

7. Apollo Server: Implementasi

Apollo Server adalah server GraphQL paling populer untuk Node.js. Ia menyediakan semua yang dibutuhkan untuk membangun GraphQL API: schema validation, execution engine, integrasi dengan Express, dan Apollo Sandbox untuk testing.

Instalasi dan Setup

Bash
# Instal dependencies
npm install @apollo/server graphql

# Untuk Express integration
npm install @apollo/server express cors body-parser

Membuat Server GraphQL

JavaScript — Apollo Server
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// Data dummy (di production, gunakan database)
const users = [
  { id: '1', nama: 'Budi', email: 'budi@mail.com', umur: 25 },
  { id: '2', nama: 'Sari', email: 'sari@mail.com', umur: 28 },
  { id: '3', nama: 'Andi', email: 'andi@mail.com', umur: 30 },
];

const posts = [
  { id: '1', judul: 'Belajar GraphQL', konten: 'GraphQL itu...', authorId: '1', published: true },
  { id: '2', judul: 'Tips API Design', konten: 'API yang baik...', authorId: '1', published: false },
  { id: '3', judul: 'Tutorial Apollo', konten: 'Apollo Server...', authorId: '2', published: true },
];

// Type Definitions (Schema)
const typeDefs = `
  type User {
    id: ID!
    nama: String!
    email: String!
    umur: Int
    posts: [Post!]!
  }

  type Post {
    id: ID!
    judul: String!
    konten: String
    published: Boolean!
    author: User!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
    posts(published: Boolean): [Post!]!
  }

  type Mutation {
    createUser(nama: String!, email: String!, umur: Int): User!
    createPost(judul: String!, konten: String, authorId: ID!): Post!
  }
`;

// Resolvers
const resolvers = {
  Query: {
    users: () => users,
    user: (_, { id }) => users.find(u => u.id === id),
    posts: (_, { published }) => {
      if (published !== undefined) {
        return posts.filter(p => p.published === published);
      }
      return posts;
    },
  },

  // Resolver untuk field-level — menangani relasi
  User: {
    posts: (parent) => posts.filter(p => p.authorId === parent.id),
  },

  Post: {
    author: (parent) => users.find(u => u.id === parent.authorId),
  },

  Mutation: {
    createUser: (_, { nama, email, umur }) => {
      const newUser = {
        id: String(users.length + 1),
        nama,
        email,
        umur,
      };
      users.push(newUser);
      return newUser;
    },

    createPost: (_, { judul, konten, authorId }) => {
      const newPost = {
        id: String(posts.length + 1),
        judul,
        konten,
        authorId,
        published: false,
      };
      posts.push(newPost);
      return newPost;
    },
  },
};

// Buat dan jalankan server
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
});

console.log(`🚀 Server GraphQL berjalan di ${url}`);
// Output: 🚀 Server GraphQL berjalan di http://localhost:4000/

Testing di Apollo Sandbox

Query — Apollo Sandbox
# Buka http://localhost:4000 di browser untuk Apollo Sandbox

# Test query:
query {
  users {
    nama
    email
    posts {
      judul
      published
    }
  }
}

# Test mutation:
mutation {
  createUser(nama: "Maya", email: "maya@mail.com", umur: 22) {
    id
    nama
    email
  }
}

# Test mutation dengan response data:
mutation {
  createPost(judul: "Post dari Sandbox", konten: "Testing!", authorId: "1") {
    id
    judul
    author {
      nama
    }
  }
}
💡 Tips

Apollo Sandbox otomatis mendeteksi schema Anda dan menyediakan autocomplete, validasi, dan dokumentasi interaktif. Ini sangat membantu saat development karena Anda bisa langsung mencoba query tanpa perlu Postman atau tool API lainnya.

8. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang GraphQL:

Pertanyaan 1: Apa keunggulan utama GraphQL dibanding REST dalam hal pengambilan data?

a) GraphQL lebih cepat dari REST
b) Klien bisa meminta data spesifik yang dibutuhkan — tidak over-fetching atau under-fetching
c) GraphQL tidak memerlukan server
d) GraphQL hanya untuk database MySQL

Pertanyaan 2: Operasi apa yang digunakan untuk mengambil data di GraphQL?

a) Mutation
b) Subscription
c) Query
d) Request

Pertanyaan 3: Apa fungsi dari tanda "!" (tanda seru) dalam schema GraphQL?

a) Field bersifat opsional
b) Field bersifat wajib (non-nullable)
c) Field bisa berupa array
d) Field hanya bisa dibaca

Pertanyaan 4: Teknologi apa yang digunakan oleh Subscription di GraphQL?

a) HTTP polling
b) Server-Sent Events
c) WebSocket
d) FTP

Pertanyaan 5: Apa peran dari "resolver" dalam GraphQL?

a) Mendefinisikan schema database
b) Menangani logika pengambilan/pengubahan data untuk setiap field
c) Mengelola autentikasi user
d) Mengkompilasi kode JavaScript
🔍 Zoom
100%
🎨 Tema