Salin dan Bagikan
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/
Editor : Hendra WIjaya
Publisher :
Tirinfo
Read : 7 minutes.
Update : 7 January 2026