GraphQL Complete Guide — Panduan Lengkap API Query Language Modern
Pengenalan: Revolusi API Development dengan GraphQL
GraphQL merepresentasikan paradigm shift fundamental dalam cara kita design dan consume APIs. Diperkenalkan oleh Facebook (sekarang Meta) pada 2015, GraphQL telah menjadi alternative powerful terhadap REST architecture, menawarkan flexibility, efficiency, dan developer experience yang superior.
Berbeda dengan REST yang menggunakan multiple endpoints dengan fixed response shapes, GraphQL menggunakan single endpoint dengan query language yang powerful. Clients dapat request exactly data yang mereka butuhkan, mengeliminasi over-fetching dan under-fetching problems yang umum terjadi di REST APIs. Dalam panduan comprehensive ini, kita akan explore setiap aspek GraphQL dari fundamental concepts hingga advanced patterns dan production deployment strategies.
Daftar Isi
- GraphQL Fundamentals
- Type System dan Schema
- Queries
- Mutations
- Subscriptions
- Resolvers dan Field Resolution
- Error Handling
- Caching Strategies
- N+1 Problem dan DataLoader
- Authentication dan Authorization
- Real-World Patterns
- Performance Optimization
- Kesimpulan
GraphQL Fundamentals
REST vs GraphQL
REST (Multiple Endpoints):
GET /api/users/1 → { id, name, email, address, posts }
GET /api/users/1/posts → [{ id, title, content }]
GET /api/posts/1/comments → [{ id, text, author }]
Problem: Over-fetching (menerima data yang tidak dibutuhkan)
GraphQL (Single Endpoint):
POST /graphql
Query: {
user(id: 1) {
name
email
posts { title }
}
}
Response: Exactly what was requested!
GraphQL Advantages
- Strongly Typed Schema: Self-documenting, IDE autocomplete
- Single Endpoint: Simplified API, easier versioning
- Precise Data Fetching: Only get what you need
- Powerful Developer Tools: GraphiQL, Apollo DevTools
- Real-time Capabilities: Subscriptions untuk live data
Type System dan Schema
Type system mendefinisikan struktur data dan operasi yang available. Ini adalah contract antara client dan server.
Basic Type Definitions
# Scalar Types
type User {
id: ID! # Non-nullable
name: String!
email: String!
age: Int
isActive: Boolean!
createdAt: String!
}
# List Types
type Post {
id: ID!
title: String!
content: String!
author: User! # Reference ke User
tags: [String!]! # Non-null list of non-null strings
}
# Input Types (untuk mutations)
input CreatePostInput {
title: String!
content: String!
authorId: ID!
}
# Custom Scalar Types
scalar DateTime
scalar JSON
# Enums
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
Relationships dan Nested Types
Relationships mendefinisikan bagaimana types berbeda berhubungan. GraphQL’s strength adalah kemampuannya fetch nested related data dalam single request.
type User {
id: ID!
name: String!
posts: [Post!]!
followers: [User!]!
following: [User!]!
}
type Post {
id: ID!
title: String!
author: User!
comments: [Comment!]!
likes: [User!]!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
createdAt: DateTime!
}
Queries
Queries fetch data dari GraphQL server. Clients specify exactly data yang mereka butuhkan, eliminating over/under-fetching problems dari REST.
Simple Queries
# Query Definition
type Query {
user(id: ID!): User
posts(limit: Int, offset: Int): [Post!]!
searchPosts(query: String!): [Post!]!
}
# Client Query
query GetUser {
user(id: "1") {
id
name
email
}
}
# Query dengan Arguments
query GetPosts {
posts(limit: 10, offset: 0) {
id
title
author {
name
}
}
}
# Aliases (request same field dengan berbeda name)
query GetUserData {
currentUser: user(id: "1") {
name
email
}
otherUser: user(id: "2") {
name
email
}
}
# Fragments (reusable query parts)
fragment UserFields on User {
id
name
email
createdAt
}
query GetUsers {
user1: user(id: "1") {
...UserFields
}
user2: user(id: "2") {
...UserFields
}
}
Complex Queries
Complex queries combine multiple features seperti variables, aliases, fragments, dan directives untuk powerful data fetching patterns.
query GetUserWithPosts {
user(id: "1") {
id
name
email
posts {
id
title
comments {
text
author {
name
}
}
}
}
}
# Query dengan Variables
query GetUserPosts($userId: ID!, $limit: Int!) {
user(id: $userId) {
name
posts(limit: $limit) {
title
content
}
}
}
# Variables JSON:
{
"userId": "1",
"limit": 5
}
# Directives
query GetPostsConditional($includeTags: Boolean!) {
posts {
id
title
tags @include(if: $includeTags)
}
}
# @skip directive
query GetPosts($skipComments: Boolean!) {
posts {
id
title
comments @skip(if: $skipComments) {
text
}
}
}
Mutations
Mutations modify server state dan return updated data. Mereka adalah GraphQL equivalent dari POST/PUT/DELETE dalam REST.
Basic Mutations
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
}
input CreateUserInput {
name: String!
email: String!
password: String!
}
input UpdateUserInput {
name: String
email: String
age: Int
}
# Client Mutation
mutation CreateNewUser {
createUser(input: {
name: "John Doe"
email: "[email protected]"
password: "secure123"
}) {
id
name
email
createdAt
}
}
# Mutation dengan Variables
mutation UpdateUserProfile($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
name
email
updatedAt
}
}
# Multiple Mutations
mutation CreateAndUpdate {
createPost(input: {
title: "New Post"
content: "Content"
authorId: "1"
}) {
id
title
}
updateUser(id: "1", input: { name: "Updated Name" }) {
id
name
}
}
Subscriptions
Real-Time Subscriptions
type Subscription {
postCreated: Post!
userFollowed(userId: ID!): User!
commentAdded(postId: ID!): Comment!
}
# Client Subscription
subscription OnPostCreated {
postCreated {
id
title
author {
name
}
}
}
# Listen untuk specific events
subscription OnUserFollowed($userId: ID!) {
userFollowed(userId: $userId) {
id
name
followers {
name
}
}
}
Resolvers dan Field Resolution
Basic Resolvers (Node.js dengan Apollo Server)
const typeDefs = `
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
posts: [Post!]!
}
`;
const resolvers = {
Query: {
user: (parent, args, context, info) => {
return users.find(u => u.id === args.id);
},
posts: () => {
return posts;
}
},
User: {
posts: (parent) => {
// parent adalah user object
return posts.filter(p => p.authorId === parent.id);
}
},
Post: {
author: (parent) => {
return users.find(u => u.id === parent.authorId);
}
}
};
Context dan Dependency Injection
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
// Get user dari JWT token
const token = req.headers.authorization || '';
const user = await verifyToken(token);
return {
user,
dataSources: {
userAPI: new UserAPI(),
postAPI: new PostAPI()
}
};
}
});
// Using context di resolver
const resolvers = {
Query: {
me: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return context.user;
},
posts: (parent, args, context) => {
return context.dataSources.postAPI.getPosts();
}
}
};
Error Handling
GraphQL Error Handling
# Custom error types
type Error {
message: String!
code: String!
path: [String!]
}
type User {
id: ID!
name: String!
}
union UserResult = User | Error
type Query {
user(id: ID!): UserResult!
}
// Error handling dalam resolvers
const resolvers = {
Query: {
user: async (parent, args) => {
try {
const user = await db.users.findById(args.id);
if (!user) {
return {
__typename: 'Error',
message: 'User not found',
code: 'USER_NOT_FOUND'
};
}
return {
__typename: 'User',
...user
};
} catch (error) {
throw new GraphQLError('Database error', {
extensions: {
code: 'INTERNAL_SERVER_ERROR',
originalError: error
}
});
}
}
}
};
// Error formatting
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: (error) => {
console.error(error);
return {
message: error.message,
code: error.extensions?.code,
path: error.path
};
}
});
Caching Strategies
HTTP Caching Headers
const resolvers = {
Query: {
posts: (parent, args, context, info) => {
// Set cache headers
context.res.set('Cache-Control', 'max-age=300'); // 5 minutes
return posts;
},
user: (parent, args, context) => {
// Private data - no cache
context.res.set('Cache-Control', 'private, no-cache');
return user;
}
}
};
Response Caching
const cacheMap = new Map();
const resolvers = {
Query: {
posts: async (parent, args) => {
const cacheKey = `posts:${JSON.stringify(args)}`;
if (cacheMap.has(cacheKey)) {
return cacheMap.get(cacheKey);
}
const posts = await fetchFromDB();
cacheMap.set(cacheKey, posts);
// Clear cache after 5 minutes
setTimeout(() => cacheMap.delete(cacheKey), 5 * 60 * 1000);
return posts;
}
}
};
N+1 Problem dan DataLoader
The N+1 Problem
// ✗ BAD: N+1 Query Problem
const resolvers = {
Query: {
users: async () => {
return await db.users.find(); // 1 query
}
},
User: {
posts: async (parent) => {
return await db.posts.find({ userId: parent.id }); // N queries!
}
}
};
// ✓ GOOD: Using DataLoader
import DataLoader from 'dataloader';
const postsByUserLoader = new DataLoader(async (userIds) => {
const postsMap = await db.posts.find({ userId: { $in: userIds } });
return userIds.map(id => postsMap[id]);
});
const resolvers = {
Query: {
users: async () => {
return await db.users.find();
}
},
User: {
posts: async (parent, args, context) => {
return context.postsByUserLoader.load(parent.id);
}
}
};
// Context setup
const context = () => ({
postsByUserLoader: new DataLoader(async (userIds) => {
// Batch query semua posts dalam 1 go
const posts = await db.posts.find({ userId: { $in: userIds } });
return userIds.map(id => posts.filter(p => p.userId === id));
})
});
Authentication dan Authorization
JWT Authentication
const typeDefs = `
type User {
id: ID!
name: String!
email: String!
}
type AuthPayload {
token: String!
user: User!
}
type Mutation {
login(email: String!, password: String!): AuthPayload!
signup(name: String!, email: String!, password: String!): AuthPayload!
}
`;
const resolvers = {
Mutation: {
login: async (parent, { email, password }) => {
const user = await db.users.findOne({ email });
if (!user || !await bcrypt.compare(password, user.password)) {
throw new Error('Invalid credentials');
}
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
return { token, user };
},
signup: async (parent, { name, email, password }) => {
const existingUser = await db.users.findOne({ email });
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await bcrypt.hash(password, 10);
const user = await db.users.create({
name,
email,
password: hashedPassword
});
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
return { token, user };
}
}
};
Authorization Directives
const typeDefs = `
directive @auth on FIELD_DEFINITION
directive @role(role: String!) on FIELD_DEFINITION
type Query {
me: User @auth
adminPanel: String @role(role: "ADMIN")
}
`;
const authDirective = {
'@auth': (resolve) => async (...args) => {
const context = args[2];
if (!context.user) {
throw new Error('Not authenticated');
}
return resolve(...args);
}
};
const roleDirective = {
'@role': (resolve, parent, args) => async (...resolveArgs) => {
const context = resolveArgs[2];
if (!context.user || context.user.role !== args.role) {
throw new Error('Insufficient permissions');
}
return resolve(...resolveArgs);
}
};
Real-World Patterns
Connection-based Pagination (Relay Cursor Pagination)
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type PostEdge {
node: Post!
cursor: String!
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type Query {
posts(first: Int, after: String): PostConnection!
}
Aggregations dan Analytics
type PostStats {
totalPosts: Int!
avgCommentsPerPost: Float!
mostLikedPost: Post
tagCloud: [TagCount!]!
}
type Query {
stats: PostStats!
}
Performance Optimization
Query Complexity Analysis
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createComplexityLimitRule({
maxComplexity: 1000,
variables: {},
onComplete: (complexity) => {
console.log('Query complexity:', complexity);
}
})
]
});
Kesimpulan
GraphQL merupakan powerful tool yang transform cara kita build dan consume APIs. Dengan strong type system, precise data fetching, dan real-time capabilities, GraphQL enable development yang lebih efficient dan scalable. Menguasai GraphQL—dari schema design hingga advanced patterns dan optimization techniques—membuat Kamu valuable asset dalam modern API development landscape.
Checklist untuk Production GraphQL API:
- ✓ Well-designed schema dengan clear types dan relationships
- ✓ Proper error handling dan validation
- ✓ Authentication dan authorization mechanisms
- ✓ DataLoader untuk solve N+1 problems
- ✓ Query complexity limits untuk prevent abuse
- ✓ Caching strategies (HTTP dan response caching)
- ✓ Monitoring dan logging
- ✓ Documentation dengan comments dan descriptions
- ✓ Testing (unit, integration, schema validation)
- ✓ Performance profiling dan optimization
type Query { user(id: ID!): User posts: [Post!]! }
type Mutation { createPost(title: String!, content: String!): Post! updateUser(id: ID!, name: String!): User! }
## Queries
```graphql
query GetUser {
user(id: "1") {
name
email
posts {
title
}
}
}
Popular GraphQL Servers
- Apollo Server
- GraphQL Yoga
- Hasura
- GraphQL-js
Benefits
✓ Efficient data fetching ✓ Strong typing ✓ Introspection ✓ Developer experience
GraphQL adalah future of API development!