Salin dan Bagikan
Cara Menggunakan GraphQL untuk API Development - Panduan lengkap GraphQL untuk API development dengan contoh implementasi praktis

Cara Menggunakan GraphQL untuk API Development

GraphQL adalah query language untuk API yang memberikan flexibility dalam request data. Mari pelajari cara menggunakannya.

Apa itu GraphQL?

REST vs GraphQL

REST:
- Multiple endpoints
- Over/under fetching
- Fixed response structure
- Versioning dengan URL

GraphQL:
- Single endpoint
- Request exact data needed
- Flexible response
- Schema evolution tanpa versioning

Contoh Comparison

REST - Multiple requests:
GET /users/1
GET /users/1/posts
GET /users/1/followers

GraphQL - Single request:
query {
  user(id: 1) {
    name
    posts {
      title
    }
    followers {
      name
    }
  }
}

Setup GraphQL Server

dengan Node.js dan Apollo Server

# Initialize project
npm init -y

# Install dependencies
npm install @apollo/server graphql
npm install typescript @types/node ts-node --save-dev

# Create tsconfig.json
npx tsc --init

Basic Server

// src/index.ts
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";

// Type definitions
const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }

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

  type Query {
    users: [User!]!
    user(id: ID!): User
    posts: [Post!]!
    post(id: ID!): Post
  }
`;

// Sample data
const users = [
  { id: "1", name: "Budi", email: "budi@example.com" },
  { id: "2", name: "Ani", email: "ani@example.com" },
];

const posts = [
  { id: "1", title: "First Post", content: "Content 1", authorId: "1" },
  { id: "2", title: "Second Post", content: "Content 2", authorId: "1" },
  { id: "3", title: "Third Post", content: "Content 3", authorId: "2" },
];

// Resolvers
const resolvers = {
  Query: {
    users: () => users,
    user: (_: any, args: { id: string }) =>
      users.find((user) => user.id === args.id),
    posts: () => posts,
    post: (_: any, args: { id: string }) =>
      posts.find((post) => post.id === args.id),
  },
  User: {
    posts: (parent: { id: string }) =>
      posts.filter((post) => post.authorId === parent.id),
  },
  Post: {
    author: (parent: { authorId: string }) =>
      users.find((user) => user.id === parent.authorId),
  },
};

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

const startServer = async () => {
  const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
  });
  console.log(`🚀 Server ready at: ${url}`);
};

startServer();

Schema Definition

Scalar Types

# Built-in scalar types
type Example {
  id: ID! # Unique identifier
  name: String! # UTF-8 string
  age: Int # 32-bit integer
  price: Float # Double-precision float
  active: Boolean # true/false
}

# Custom scalar
scalar DateTime
scalar JSON

Object Types

type User {
  id: ID!
  name: String!
  email: String!
  createdAt: DateTime!
  profile: Profile
  posts: [Post!]!
}

type Profile {
  bio: String
  avatar: String
  website: String
}

type Post {
  id: ID!
  title: String!
  content: String!
  published: Boolean!
  author: User!
  tags: [String!]!
  comments: [Comment!]!
}

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

Input Types

input CreateUserInput {
  name: String!
  email: String!
  password: String!
}

input UpdateUserInput {
  name: String
  email: String
  bio: String
}

input PostFilterInput {
  published: Boolean
  authorId: ID
  tag: String
}

Enums

enum Role {
  USER
  ADMIN
  MODERATOR
}

enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

type User {
  id: ID!
  name: String!
  role: Role!
}

Interfaces

interface Node {
  id: ID!
}

interface Timestamped {
  createdAt: DateTime!
  updatedAt: DateTime!
}

type User implements Node & Timestamped {
  id: ID!
  name: String!
  createdAt: DateTime!
  updatedAt: DateTime!
}

Unions

union SearchResult = User | Post | Comment

type Query {
  search(term: String!): [SearchResult!]!
}

Queries

Basic Query

# Schema
type Query {
  users: [User!]!
  user(id: ID!): User
  posts(filter: PostFilterInput, limit: Int, offset: Int): [Post!]!
}

# Client query
query GetUser {
  user(id: "1") {
    name
    email
    posts {
      title
    }
  }
}

# Query with variables
query GetUser($userId: ID!) {
  user(id: $userId) {
    name
    email
  }
}

# Variables
{
  "userId": "1"
}

Query with Arguments

# Schema
type Query {
  posts(
    limit: Int = 10
    offset: Int = 0
    published: Boolean
    orderBy: PostOrderBy
  ): [Post!]!
}

enum PostOrderBy {
  CREATED_AT_ASC
  CREATED_AT_DESC
  TITLE_ASC
  TITLE_DESC
}

# Client query
query GetPosts {
  posts(limit: 5, published: true, orderBy: CREATED_AT_DESC) {
    id
    title
    createdAt
  }
}

Fragments

# Define fragment
fragment UserBasicInfo on User {
  id
  name
  email
}

fragment PostInfo on Post {
  id
  title
  content
  createdAt
}

# Use fragments
query GetData {
  user(id: "1") {
    ...UserBasicInfo
    posts {
      ...PostInfo
    }
  }
}

Aliases

query GetMultipleUsers {
  firstUser: user(id: "1") {
    name
  }
  secondUser: user(id: "2") {
    name
  }
}

Mutations

Basic Mutations

# Schema
type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User
  deleteUser(id: ID!): Boolean!

  createPost(input: CreatePostInput!): Post!
  publishPost(id: ID!): Post
  deletePost(id: ID!): Boolean!
}

input CreateUserInput {
  name: String!
  email: String!
  password: String!
}

input CreatePostInput {
  title: String!
  content: String!
  published: Boolean = false
}

# Client mutation
mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    name
    email
  }
}

# Variables
{
  "input": {
    "name": "Budi",
    "email": "budi@example.com",
    "password": "secret123"
  }
}

Resolver Implementation

const resolvers = {
  Mutation: {
    createUser: async (_: any, { input }: { input: CreateUserInput }) => {
      const user = await db.user.create({
        data: {
          name: input.name,
          email: input.email,
          password: await hashPassword(input.password),
        },
      });
      return user;
    },

    updateUser: async (
      _: any,
      { id, input }: { id: string; input: UpdateUserInput }
    ) => {
      const user = await db.user.update({
        where: { id },
        data: input,
      });
      return user;
    },

    deleteUser: async (_: any, { id }: { id: string }) => {
      await db.user.delete({ where: { id } });
      return true;
    },
  },
};

Subscriptions

Setup Subscriptions

// Install additional dependencies
// npm install graphql-ws ws

import { createServer } from "http";
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/lib/use/ws";
import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import { makeExecutableSchema } from "@graphql-tools/schema";
import { PubSub } from "graphql-subscriptions";

const pubsub = new PubSub();

const typeDefs = `#graphql
  type Subscription {
    postCreated: Post!
    commentAdded(postId: ID!): Comment!
  }
`;

const resolvers = {
  Subscription: {
    postCreated: {
      subscribe: () => pubsub.asyncIterator(["POST_CREATED"]),
    },
    commentAdded: {
      subscribe: (_: any, { postId }: { postId: string }) => {
        return pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]);
      },
    },
  },
  Mutation: {
    createPost: async (_: any, { input }: { input: CreatePostInput }) => {
      const post = await db.post.create({ data: input });
      pubsub.publish("POST_CREATED", { postCreated: post });
      return post;
    },
  },
};

Client Subscription

subscription OnPostCreated {
  postCreated {
    id
    title
    author {
      name
    }
  }
}

Authentication

Context Setup

import { ApolloServer } from "@apollo/server";
import jwt from "jsonwebtoken";

interface Context {
  user?: {
    id: string;
    role: string;
  };
}

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

const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => {
    const token = req.headers.authorization?.replace("Bearer ", "");

    if (token) {
      try {
        const user = jwt.verify(token, process.env.JWT_SECRET!) as {
          id: string;
          role: string;
        };
        return { user };
      } catch {
        return {};
      }
    }

    return {};
  },
  listen: { port: 4000 },
});

Protected Resolvers

const resolvers = {
  Query: {
    me: (_: any, __: any, context: Context) => {
      if (!context.user) {
        throw new Error("Not authenticated");
      }
      return db.user.findUnique({ where: { id: context.user.id } });
    },
  },

  Mutation: {
    createPost: (_: any, { input }: any, context: Context) => {
      if (!context.user) {
        throw new Error("Not authenticated");
      }
      return db.post.create({
        data: {
          ...input,
          authorId: context.user.id,
        },
      });
    },

    deleteUser: (_: any, { id }: { id: string }, context: Context) => {
      if (!context.user || context.user.role !== "ADMIN") {
        throw new Error("Not authorized");
      }
      return db.user.delete({ where: { id } });
    },
  },
};

Error Handling

Custom Errors

import { GraphQLError } from "graphql";

const resolvers = {
  Mutation: {
    createUser: async (_: any, { input }: any) => {
      const existingUser = await db.user.findUnique({
        where: { email: input.email },
      });

      if (existingUser) {
        throw new GraphQLError("Email already exists", {
          extensions: {
            code: "USER_ALREADY_EXISTS",
            http: { status: 400 },
          },
        });
      }

      return db.user.create({ data: input });
    },
  },
};

Error Response

{
  "errors": [
    {
      "message": "Email already exists",
      "locations": [{ "line": 2, "column": 3 }],
      "path": ["createUser"],
      "extensions": {
        "code": "USER_ALREADY_EXISTS"
      }
    }
  ],
  "data": null
}

DataLoader (N+1 Problem)

Install DataLoader

npm install dataloader

Implementation

import DataLoader from "dataloader";

// Batch function
const batchUsers = async (userIds: readonly string[]) => {
  const users = await db.user.findMany({
    where: { id: { in: [...userIds] } },
  });

  // Return dalam order yang sama dengan input
  const userMap = new Map(users.map((u) => [u.id, u]));
  return userIds.map((id) => userMap.get(id) || null);
};

// Create loader per request
const context = async ({ req }: any) => ({
  loaders: {
    userLoader: new DataLoader(batchUsers),
  },
});

// Use in resolver
const resolvers = {
  Post: {
    author: (parent: any, _: any, context: any) => {
      return context.loaders.userLoader.load(parent.authorId);
    },
  },
};

Best Practices

Schema Design

# Use meaningful names
type User {
  # Good
  createdAt: DateTime!
  updatedAt: DateTime!

  # Avoid
  created: String!
  updated: String!
}

# Nullable by default, use ! for required
type Post {
  id: ID! # Required
  title: String! # Required
  subtitle: String # Optional
}

# Use Input types untuk mutations
input CreateUserInput {
  name: String!
  email: String!
}

# Return object dari mutation (bukan Boolean)
type Mutation {
  # Good
  createUser(input: CreateUserInput!): User!

  # Avoid
  createUser(name: String!, email: String!): Boolean!
}

Performance

// Use DataLoader
// Implement pagination
// Use query complexity analysis
// Cache when appropriate

// Pagination
type Query {
  posts(
    first: Int
    after: String
    last: Int
    before: String
  ): PostConnection!
}

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

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

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

Kesimpulan

GraphQL memberikan flexibility dan efficiency dalam API development. Ideal untuk complex data requirements dengan multiple interconnected resources.

Artikel Terkait

Link Postingan : https://www.tirinfo.com/cara-menggunakan-graphql-api/

Hendra WIjaya
Tirinfo
7 minutes.
7 January 2026