GraphQL Basics — API

GraphQL adalah query language untuk API yang dikembangkan oleh Facebook (2015). Perbedaan utama dengan REST: Aspek REST GraphQL Endpoint Banyak (/users, /posts)

GraphQL adalah query language untuk API yang dikembangkan oleh Facebook (2015).

Perbedaan utama dengan REST:

Aspek REST GraphQL
Endpoint Banyak (/users, /posts) Satu (/graphql)
Data Server tentukan Client tentukan
Over-fetching Sering terjadi Tidak ada
Under-fetching Perlu multiple request Satu request cukup

Schema & Types:

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  author: User!
}

Query (mengambil data):

query {
  user(id: 1) {
    name
    email
    posts {
      title
    }
  }
}

Client memilih field yang dibutuhkan — tidak ada data berlebihan.

Mutation (mengubah data):

mutation {
  createUser(name: "Budi", email: "[email protected]") {
    id
    name
  }
}

Resolver: Fungsi di server yang mengambil data untuk setiap field di schema.

Kapan pakai GraphQL vs REST:

N+1 Problem di GraphQL — Tantangan Utama

GraphQL mudah menyebabkan N+1 query di database — lebih sering daripada REST! Karena client bisa request nested data apapun.

Contoh masalah:

query {
  users {       # 1 query: SELECT * FROM users
    name
    posts {     # N query: untuk TIAP user, SELECT * FROM posts WHERE user_id = ?
      title
    }
  }
}

Kalau ada 100 user, server eksekusi 1 + 100 = 101 query. Satu GraphQL request yang terlihat simple bisa menumbangkan database.

Solusi: DataLoader

DataLoader adalah library yang batch + cache query di dalam 1 request tick.

import DataLoader from "dataloader";

// Inisialisasi per-request (penting: jangan share antar request!)
function createPostsByUserLoader() {
  return new DataLoader(async (userIds) => {
    // 1 query saja untuk semua user IDs
    const posts = await db.query(
      "SELECT * FROM posts WHERE user_id = ANY($1)",
      [userIds]
    );

    // Group hasil sesuai urutan userIds
    return userIds.map(id => posts.filter(p => p.user_id === id));
  });
}

// Di resolver
const resolvers = {
  User: {
    posts: (user, args, context) => {
      return context.postsByUserLoader.load(user.id);
      // DataLoader batch semua .load() call dalam 1 tick → 1 SQL query
    },
  },
};

// Apollo Server — per-request context
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => ({
    postsByUserLoader: createPostsByUserLoader(),
  }),
});

Hasilnya: 101 query → 2 query (users + batched posts). DataLoader adalah must-have untuk production GraphQL.

Query Depth & Complexity Limits (Security)

GraphQL flexibility = attack surface. Malicious client bisa kirim query yang nested sangat dalam:

query EvilQuery {
  users {
    posts {
      author {
        posts {
          author {
            posts {
              # ...100 level lebih
            }
          }
        }
      }
    }
  }
}

Eksponensial — server crash dalam detik. Solusi:

import depthLimit from "graphql-depth-limit";
import { createComplexityLimitRule } from "graphql-validation-complexity";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(7),                               // max 7 level nesting
    createComplexityLimitRule(1000, {            // compute cost total
      onCost: (cost) => console.log(`Query cost: ${cost}`),
    }),
  ],
});

Setiap field punya cost. Query "mahal" (list, nested) ditolak sebelum eksekusi.

Subscriptions — Real-Time

GraphQL bukan cuma query/mutation. Subscription = push data dari server via WebSocket saat event terjadi.

type Subscription {
  newMessage(chatId: ID!): Message!
}
import { PubSub } from "graphql-subscriptions";
const pubsub = new PubSub();

const resolvers = {
  Subscription: {
    newMessage: {
      subscribe: (_, { chatId }) => pubsub.asyncIterator(`NEW_MSG_${chatId}`),
    },
  },
  Mutation: {
    sendMessage: async (_, { chatId, text }) => {
      const msg = await db.messages.create({ chatId, text });
      pubsub.publish(`NEW_MSG_${chatId}`, { newMessage: msg });
      return msg;
    },
  },
};

Client (apollo-client):

useSubscription(NEW_MESSAGE_SUBSCRIPTION, { variables: { chatId } });
// React component auto-update saat message baru masuk

Cocok untuk: chat, notifications, live dashboards, multiplayer.

Pagination — Cursor-Based (Relay Spec)

REST boleh pakai ?page=5. GraphQL best practice: cursor-based (Relay connection spec).

type Query {
  posts(first: Int, after: String): PostConnection!
}

type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}

type PostEdge {
  cursor: String!
  node: Post!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}

Keuntungan: stable saat data ada insert (vs offset yang bisa duplicate/skip).

Error Handling

GraphQL tidak pakai HTTP status untuk error bisnis (selalu 200). Error di dalam response body:

{
  "data": { "user": null },
  "errors": [
    { "message": "User not found", "path": ["user"], "extensions": { "code": "NOT_FOUND" } }
  ]
}

Client cek errors array + extensions.code untuk branching. Untuk error yang perlu stop request sepenuhnya (auth), pakai HTTP 401 di level transport.

Yang akan kamu pelajari