GraphQL là gì? Hướng dẫn đầy đủ cho lập trình viên

REST đã là kiến trúc API thống trị hơn một thập kỷ. Nhưng khi ứng dụng ngày càng phức tạp—với mobile clients cần dữ liệu khác web clients, và frontend teams phải chờ backend thay đổi—một cách tiếp cận mới xuất hiện. Năm 2015, Facebook open-source GraphQL, một ngôn ngữ truy vấn cho phép clients yêu cầu chính xác dữ liệu họ cần.
Trong hướng dẫn toàn diện này, bạn sẽ học mọi thứ cần biết về GraphQL—từ khái niệm cốt lõi đến các patterns triển khai thực tế.
Bạn sẽ học được gì
✅ GraphQL là gì và những vấn đề nó giải quyết
✅ GraphQL khác REST APIs như thế nào
✅ Định nghĩa schema với types, queries và mutations
✅ Viết queries, mutations và subscriptions
✅ Cách resolvers hoạt động và triển khai chúng
✅ Authentication, error handling và pagination
✅ N+1 problem và DataLoader pattern
✅ Khi nào nên dùng GraphQL vs REST
Yêu cầu kiến thức
Trước khi bắt đầu, bạn nên có:
- Hiểu biết cơ bản về APIs (xem Hướng dẫn REST API)
- Quen thuộc với giao thức HTTP
- Kiến thức JavaScript/TypeScript cơ bản (ví dụ sử dụng Node.js)
Phần 1: Hiểu về GraphQL
GraphQL là gì?
GraphQL là một ngôn ngữ truy vấn cho APIs và một runtime để thực thi các truy vấn đó với dữ liệu của bạn. Khác với REST, nơi server quyết định trả về dữ liệu gì, GraphQL cho phép client chỉ định chính xác dữ liệu cần thiết.
Đặc điểm chính:
- Client-driven: Clients yêu cầu các fields cụ thể, không phải toàn bộ resources
- Single endpoint: Tất cả operations đi qua một URL (thường là
/graphql) - Strongly typed: Mỗi API có schema định nghĩa tất cả dữ liệu và operations
- Introspective: Clients có thể query chính schema để khám phá những gì có sẵn
Các vấn đề GraphQL giải quyết
1. Over-fetching (Lấy dư dữ liệu)
Với REST, endpoints trả về cấu trúc dữ liệu cố định. Nếu bạn chỉ cần tên user, bạn vẫn nhận được email, address, phone và mọi thứ khác.
REST: GET /api/users/1
Response: { id, name, email, phone, address, avatar, bio, createdAt, ... }
↑ Bạn chỉ cần name!# GraphQL: Yêu cầu chính xác những gì bạn cần
query {
user(id: 1) {
name
}
}
# Response: { "data": { "user": { "name": "Chanh Le" } } }2. Under-fetching (Lấy thiếu dữ liệu)
Cần posts của user và comments của mỗi post? Với REST, đó là nhiều requests.
REST (3 requests):
GET /api/users/1
GET /api/users/1/posts
GET /api/posts/42/comments (cho mỗi post!)# GraphQL (1 request):
query {
user(id: 1) {
name
posts {
title
comments {
text
author { name }
}
}
}
}3. API Evolution không cần Versioning
REST APIs thường cần versioning (/v1/users, /v2/users) khi cấu trúc dữ liệu thay đổi. GraphQL tránh điều này—bạn thêm fields mới mà không breaking existing clients, và deprecate fields cũ một cách mượt mà.
type User {
name: String!
fullName: String! # Field mới được thêm
username: String @deprecated(reason: "Use 'name' instead")
}Phần 2: Schema & Type System
Schema là trái tim của mọi GraphQL API. Nó định nghĩa hợp đồng giữa client và server.
Scalar Types
GraphQL có năm scalar types built-in:
type Example {
id: ID! # Định danh duy nhất (serialize thành String)
name: String! # Chuỗi UTF-8
age: Int! # Số nguyên 32-bit
score: Float! # Số thực double-precision
active: Boolean! # true hoặc false
}Dấu ! nghĩa là field non-nullable—luôn trả về giá trị.
Object Types
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]! # Danh sách Post objects
profile: Profile # Nullable - có thể không có profile
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
tags: [String!]!
createdAt: String!
}Nullability Rules
Field | null | [] | [null] | ["hi"] | ["hi", null]
-----------|--------|--------|---------|---------|-------------
[String] ✅ ✅ ✅ ✅ ✅
[String]! ❌ ✅ ✅ ✅ ✅
[String!]! ❌ ✅ ❌ ✅ ❌Enum, Input, Interface & Union Types
# Enum - Giới hạn giá trị hợp lệ
enum Role {
ADMIN
EDITOR
VIEWER
}
# Input - Dữ liệu đầu vào cho mutations
input CreateUserInput {
name: String!
email: String!
role: Role = VIEWER
}
# Interface - Các fields chung
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
# Union - Một trong nhiều types
union SearchResult = User | Post | CommentPhần 3: Queries
Queries là cách clients đọc dữ liệu từ GraphQL API.
Basic Queries
query {
user(id: "1") {
name
email
}
}Nested Queries
Lấy dữ liệu liên quan trong một request:
query {
user(id: "1") {
name
posts {
title
comments {
text
author { name }
}
}
}
}Variables & Named Queries
query GetUserWithPosts($userId: ID!, $includeComments: Boolean!) {
user(id: $userId) {
name
posts {
title
comments @include(if: $includeComments) {
text
}
}
}
}Aliases & Fragments
# Aliases - Lấy cùng field với arguments khác nhau
query {
admin: user(id: "1") { ...UserInfo }
editor: user(id: "2") { ...UserInfo }
}
# Fragments - Tái sử dụng field selections
fragment UserInfo on User {
id
name
email
role
}Phần 4: Mutations
Mutations là cách clients tạo, cập nhật và xóa dữ liệu.
# Tạo
mutation {
createUser(input: { name: "Alice", email: "[email protected]" }) {
id
name
}
}
# Cập nhật
mutation {
updateUser(id: "1", input: { name: "Alice Updated" }) {
id
name
}
}
# Xóa
mutation {
deleteUser(id: "1")
}Phần 5: Subscriptions
Subscriptions cho phép cập nhật real-time qua WebSockets.
subscription OnNewPost {
postCreated {
id
title
author { name }
}
}Khi một post mới được tạo, server push dữ liệu đến tất cả clients đã subscribe.
Phần 6: Resolvers
Resolvers là functions lấy dữ liệu cho mỗi field trong schema.
const resolvers = {
Query: {
user: (parent, { id }, context) => {
return context.db.users.findById(id);
},
},
User: {
posts: (parent, args, context) => {
return context.db.posts.findByAuthorId(parent.id);
},
},
Post: {
author: (parent, args, context) => {
return context.db.users.findById(parent.authorId);
},
},
};Resolver nhận 4 arguments:
parent: Giá trị trả về từ parent resolverargs: Arguments truyền vào fieldcontext: Dữ liệu chia sẻ (auth, database, etc.)info: Metadata về query execution
Phần 7: N+1 Problem & DataLoader
Vấn đề N+1
query {
posts { # 1 query: SELECT * FROM posts (trả về 10 posts)
title
author { # 10 queries: SELECT * FROM users WHERE id = ?
name # (một cho mỗi post!)
}
}
}
# Tổng: 11 queries cho 10 posts!Giải pháp DataLoader
DataLoader gộp nhiều requests riêng lẻ thành một batch request:
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds) => {
// Một query: SELECT * FROM users WHERE id IN (1, 2, 3, ...)
const users = await db.users.findByIds([...userIds]);
return userIds.map((id) => users.find((u) => u.id === id));
});
// Trong resolver
const resolvers = {
Post: {
author: (parent, args, context) => {
return context.loaders.userLoader.load(parent.authorId);
},
},
};Phần 8: Authentication & Error Handling
Authentication qua Context
const server = new ApolloServer({ typeDefs, resolvers });
await startStandaloneServer(server, {
context: async ({ req }) => {
const token = req.headers.authorization?.replace('Bearer ', '');
const user = token ? await verifyJWT(token) : null;
return { user, db: database };
},
});Authorization trong Resolvers
const resolvers = {
Mutation: {
createPost: (_, { input }, context) => {
if (!context.user) {
throw new GraphQLError('Bạn phải đăng nhập', {
extensions: { code: 'UNAUTHENTICATED' },
});
}
return context.db.posts.create({ ...input, authorId: context.user.id });
},
},
};Error Handling
{
"data": { "user": null },
"errors": [{
"message": "User not found",
"extensions": { "code": "NOT_FOUND" }
}]
}Phần 9: Pagination
Offset-Based
type Query {
posts(limit: Int = 10, offset: Int = 0): PostConnection!
}
type PostConnection {
items: [Post!]!
totalCount: Int!
hasMore: Boolean!
}Cursor-Based (Relay Style)
Mạnh mẽ hơn cho real-time data:
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PostEdge {
node: Post!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}Phần 10: GraphQL vs REST — Khi nào dùng cái nào
| Đặc điểm | REST | GraphQL |
|---|---|---|
| Endpoints | Nhiều (/users, /posts) | Một (/graphql) |
| Data fetching | Server quyết định response | Client quyết định response |
| Over-fetching | Phổ biến | Loại bỏ |
| Under-fetching | Phổ biến | Loại bỏ |
| Versioning | URL-based (/v1, /v2) | Schema evolution |
| Caching | HTTP caching (dễ) | Phức tạp hơn |
| Learning curve | Thấp hơn | Cao hơn |
| Real-time | Webhooks, SSE | Built-in subscriptions |
Khi nào chọn REST
✅ APIs CRUD đơn giản với data shapes dự đoán được
✅ Public APIs cho nhiều third parties
✅ Microservices cần HTTP caching
✅ Teams mới bắt đầu phát triển API
Khi nào chọn GraphQL
✅ Nhiều client types cần dữ liệu khác nhau (mobile, web, IoT)
✅ Data relationships phức tạp với deep nesting
✅ Frontend requirements thay đổi nhanh
✅ Tính năng real-time (chat, notifications, live feeds)
✅ APIs có vấn đề over/under-fetching thực sự
Phần 11: Best Practices
Schema Design
- Dùng tên type mô tả:
User, không phảiUsr - Dùng input types cho mutations: Tách input khỏi output
- Non-nullable by default: Thêm
!trừ khi null hợp lệ - Dùng enums cho fixed values: Ngăn dữ liệu không hợp lệ
- Thiết kế cho client, không phải database
Security
- Query depth limiting: Ngăn nested queries quá sâu
- Query complexity analysis: Gán chi phí cho fields
- Rate limiting: Giới hạn queries per client
- Tắt introspection trong production
Performance
- Luôn dùng DataLoader để giải quyết N+1
- Persist queries: Gửi query IDs thay vì full query strings
- Cursor-based pagination cho datasets lớn
- Monitor resolver execution time
Tổng kết
GraphQL là một thay thế mạnh mẽ cho REST, cho phép clients kiểm soát chính xác dữ liệu của họ:
✅ GraphQL là ngôn ngữ truy vấn cho phép clients yêu cầu chính xác dữ liệu cần thiết
✅ Schemas định nghĩa API contract với strong type system
✅ Queries đọc dữ liệu, mutations sửa dữ liệu, subscriptions cho real-time
✅ Resolvers là functions lấy dữ liệu cho mỗi field
✅ DataLoader giải quyết N+1 problem bằng cách batching queries
✅ Authentication truyền qua context object
✅ Cursor-based pagination mạnh mẽ hơn offset-based
✅ GraphQL và REST đều có thế mạnh—chọn dựa trên nhu cầu
Bài viết liên quan:
REST API là gì? Hướng dẫn đầy đủ
Giao thức HTTP - Hướng dẫn đầy đủ
Kiến trúc Server-Client
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.