GraphQL Complete Guide — Panduan Lengkap API Query Language Modern

GraphQL Complete Guide — Panduan Lengkap API Query Language Modern

20/10/2025 Backend By Tech Writers
GraphQLAPIBackendDatabaseREST Alternative

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

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
    }
  }
}
  • Apollo Server
  • GraphQL Yoga
  • Hasura
  • GraphQL-js

Benefits

✓ Efficient data fetching ✓ Strong typing ✓ Introspection ✓ Developer experience

GraphQL adalah future of API development!