RESTful API Design: Nghệ thuật thiết kế API "đẹp"


Lời mở đầu

Chào mọi người! Tiếp tục series về lập trình mạng, hôm nay mình muốn chia sẻ về một chủ đề cực kỳ quan trọng trong phát triển web hiện đại: RESTful API Design.

Trong quá trình làm dự án Kanji Web (Angular + NestJS), mình đã phải thiết kế hơn 50 API endpoints. Từ những sai lầm ban đầu đến khi refactor lại toàn bộ, mình học được rất nhiều về cách thiết kế API “đẹp”, dễ sử dụng và dễ bảo trì.

🌐 REST API là gì?

Định nghĩa

REST (Representational State Transfer) là một architectural style (phong cách kiến trúc) cho việc thiết kế networked applications.

RESTful API là API tuân theo các nguyên tắc của REST.

Nguyên tắc cốt lõi (REST Constraints)

1. Client-Server Architecture

Client (Frontend)      ←→      Server (Backend)
   - UI/UX                        - Business Logic
   - Presentation                 - Database
   - User Interaction             - Authentication

Tách biệt concerns → Độc lập phát triển

2. Stateless

Mỗi request phải chứa đầy đủ thông tin

Request 1: GET /users/123 + Auth Token
Request 2: GET /posts + Auth Token
Request 3: DELETE /posts/456 + Auth Token

Server KHÔNG nhớ client từ request trước

3. Cacheable

Response nên chỉ rõ: Có cache được không?

GET /users/123
Response Headers:
  Cache-Control: max-age=3600
  ETag: "abc123"

4. Uniform Interface

API nhất quán, dễ đoán:
- GET /users → Lấy danh sách users
- GET /users/123 → Lấy user có ID 123
- POST /users → Tạo user mới
- PUT /users/123 → Cập nhật user 123
- DELETE /users/123 → Xóa user 123

5. Layered System

Client → Load Balancer → API Gateway → Service → Database

Client không cần biết có bao nhiêu layers

6. Code on Demand (Optional)

Server có thể gửi executable code (JavaScript)
Ít dùng trong practice

📋 HTTP Methods (Verbs)

CRUD Operations

HTTP MethodCRUDIdempotentSafeMục đích
GETRead✅ Yes✅ YesLấy dữ liệu
POSTCreate❌ No❌ NoTạo mới
PUTUpdate✅ Yes❌ NoCập nhật toàn bộ
PATCHUpdate❌ No❌ NoCập nhật một phần
DELETEDelete✅ Yes❌ NoXóa

Idempotent nghĩa là gì?

Idempotent: Gọi nhiều lần = Gọi 1 lần (kết quả giống nhau)

Ví dụ:

PUT /users/123 { "name": "John" }
Call 1: name = "John" ✅
Call 2: name = "John" ✅ (Không thay đổi)
Call 3: name = "John" ✅ (Không thay đổi)

POST /users { "name": "John" }
Call 1: Tạo user ID 1 ✅
Call 2: Tạo user ID 2 ❌ (Duplicate!)
Call 3: Tạo user ID 3 ❌ (Duplicate!)

Safe nghĩa là gì?

Safe: Không thay đổi server state (read-only)

  • GET: Safe ✅ (Chỉ đọc)
  • POST/PUT/DELETE: Not safe ❌ (Thay đổi data)

🎯 Thiết kế URL (Endpoint Design)

1. Nguyên tắc cơ bản

✅ Sử dụng danh từ (nouns), không dùng động từ (verbs)

❌ BAD:
GET /getUsers
POST /createUser
PUT /updateUser/123
DELETE /deleteUser/123

✅ GOOD:
GET /users
POST /users
PUT /users/123
DELETE /users/123

✅ Dùng số nhiều (plural) cho collections

❌ BAD:
GET /user
GET /post
GET /comment

✅ GOOD:
GET /users
GET /posts
GET /comments

✅ Phân cấp resources (Resource Hierarchy)

✅ GOOD:
GET /users/123/posts          → Posts của user 123
GET /users/123/posts/456      → Post 456 của user 123
GET /posts/456/comments       → Comments của post 456
GET /users/123/followers      → Followers của user 123

✅ Dùng hyphens (-), không dùng underscores (_)

❌ BAD:
GET /user_profiles
GET /kanji_collections

✅ GOOD:
GET /user-profiles
GET /kanji-collections

✅ Lowercase toàn bộ

❌ BAD:
GET /Users
GET /Kanji/Search

✅ GOOD:
GET /users
GET /kanji/search

2. Query Parameters

Filtering (Lọc):

GET /users?role=admin
GET /users?status=active
GET /posts?author=123&category=tech

Sorting (Sắp xếp):

GET /users?sort=name              → Tăng dần (ascending)
GET /users?sort=-created_at       → Giảm dần (descending)
GET /posts?sort=likes,-date       → Multi-field sort

Pagination (Phân trang):

GET /users?page=2&limit=20
GET /posts?offset=40&limit=20

Response:
{
  "data": [...],
  "pagination": {
    "total": 150,
    "page": 2,
    "limit": 20,
    "totalPages": 8
  }
}

Field Selection (Chọn fields):

GET /users?fields=id,name,email
GET /users/123?fields=name

Response chỉ chứa fields được yêu cầu
→ Giảm bandwidth

Search (Tìm kiếm):

GET /users?search=john
GET /posts?q=javascript
GET /kanji?character=日

3. Ví dụ từ Kanji Web

User Management:

GET    /users              → Danh sách users (admin)
GET    /users/:id          → Chi tiết user
PUT    /users/:id          → Cập nhật user
DELETE /users/:id          → Xóa user (admin)

Authentication:

POST   /auth/register      → Đăng ký
POST   /auth/login         → Đăng nhập
POST   /auth/refresh       → Refresh token
POST   /auth/logout        → Đăng xuất

User Profile:

GET    /user-profile                      → Profile của user hiện tại
PUT    /user-profile                      → Cập nhật profile
POST   /user-profile/change-password     → Đổi password
GET    /user-profile/learning-history    → Lịch sử học tập

Kanji:

GET    /kanji/search                      → Tìm kiếm Kanji
GET    /kanji/:character                  → Chi tiết Kanji
POST   /kanji/predict                     → AI nhận diện (đặc biệt)

Kanji Collections:

GET    /kanji-lists                       → Danh sách collections
POST   /kanji-lists                       → Tạo collection mới
GET    /kanji-lists/:id                   → Chi tiết collection
PUT    /kanji-lists/:id                   → Cập nhật collection
DELETE /kanji-lists/:id                   → Xóa collection
POST   /kanji-lists/:id/kanjis           → Thêm Kanji vào list
DELETE /kanji-lists/:id/kanjis/:kanjiId  → Xóa Kanji khỏi list

Quiz:

GET    /quiz                              → Danh sách quiz
POST   /quiz                              → Tạo quiz
GET    /quiz/:id                          → Chi tiết quiz
PUT    /quiz/:id                          → Cập nhật quiz
DELETE /quiz/:id                          → Xóa quiz
POST   /quiz/:id/questions               → Thêm câu hỏi
POST   /quiz/:id/submit                  → Nộp bài
GET    /quiz/results                     → Kết quả quiz

Community:

GET    /community/posts                   → Danh sách posts
POST   /community/posts                   → Tạo post
GET    /community/posts/:id               → Chi tiết post
PUT    /community/posts/:id               → Cập nhật post
DELETE /community/posts/:id               → Xóa post
POST   /community/posts/:id/like         → Like post
GET    /community/posts/:id/comments     → Comments của post
POST   /community/posts/:id/comments     → Tạo comment

📤 Request & Response Format

1. Request Body

JSON format:

POST /users
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "secure123",
  "displayName": "John Doe"
}

Form Data (File upload):

POST /user-profile/avatar
Content-Type: multipart/form-data

avatar: [binary file]

2. Response Format

Successful Response:

GET /users/123
Status: 200 OK

{
  "data": {
    "id": 123,
    "email": "user@example.com",
    "displayName": "John Doe",
    "createdAt": "2025-12-01T10:30:00Z"
  },
  "statusCode": 200,
  "timestamp": "2025-12-25T15:30:00Z"
}

List Response with Pagination:

GET /users?page=2&limit=10
Status: 200 OK

{
  "data": [...],
  "pagination": {
    "total": 150,
    "page": 2,
    "limit": 10,
    "totalPages": 15,
    "hasNext": true,
    "hasPrev": true
  },
  "statusCode": 200,
  "timestamp": "2025-12-25T15:30:00Z"
}

Error Response:

POST /users
Status: 400 Bad Request

{
  "statusCode": 400,
  "message": "Validation failed",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email format"
    },
    {
      "field": "password",
      "message": "Password must be at least 8 characters"
    }
  ],
  "timestamp": "2025-12-25T15:30:00Z"
}

🔢 HTTP Status Codes

Success (2xx)

CodeNameKhi nào dùng
200OKGET, PUT, PATCH thành công
201CreatedPOST tạo resource mới thành công
204No ContentDELETE thành công (không return data)

Client Errors (4xx)

CodeNameKhi nào dùng
400Bad RequestRequest sai format, validation fail
401UnauthorizedChưa authenticate (chưa login)
403ForbiddenĐã login nhưng không có permission
404Not FoundResource không tồn tại
409ConflictConflict (email đã tồn tại, …)
422Unprocessable EntityValidation error (chi tiết hơn 400)
429Too Many RequestsRate limit exceeded

Server Errors (5xx)

CodeNameKhi nào dùng
500Internal Server ErrorLỗi server không xác định
502Bad GatewayGateway/proxy error
503Service UnavailableServer quá tải hoặc maintenance

Ví dụ thực tế

Successful Operations:

GET /users/123           → 200 OK
POST /users              → 201 Created
PUT /users/123           → 200 OK
DELETE /users/123        → 204 No Content

Client Errors:

GET /users/999           → 404 Not Found
POST /auth/login         → 401 Unauthorized (wrong password)
DELETE /posts/123        → 403 Forbidden (not your post)
POST /users              → 409 Conflict (email exists)

Server Errors:

GET /users               → 500 Internal Server Error (DB crash)
POST /quiz/:id/submit    → 503 Service Unavailable (AI service down)

🔐 Authentication & Authorization

1. JWT (JSON Web Token)

Flow:

1. Client: POST /auth/login { email, password }
2. Server: Verify → Generate JWT
3. Server: Return { accessToken, refreshToken }
4. Client: Store tokens (localStorage/cookie)
5. Client: GET /users + Header: "Authorization: Bearer <token>"
6. Server: Verify JWT → Process request

JWT Structure:

header.payload.signature

{
  "sub": "user-id-123",
  "role": "admin",
  "iat": 1234567890,
  "exp": 1234571490
}

Best Practices:

  • Access token: Short-lived (15 phút)
  • Refresh token: Long-lived (30 ngày)
  • Lưu refresh token trong DB để revoke được
  • Access token không lưu DB (stateless)

2. API Keys

Khi nào dùng:

  • Server-to-server communication
  • Third-party integrations
  • Rate limiting by customer

Example:

GET /api/translate
Header: X-API-Key: abc123xyz789

hoặc

GET /api/translate?apiKey=abc123xyz789

3. OAuth 2.0

Social Login:

Login with Google/Facebook/GitHub

1. Client → OAuth Provider: Request authorization
2. User authorize
3. Provider → Client: Authorization code
4. Client → Provider: Exchange code for token
5. Client → Server: Send token
6. Server: Verify with Provider

⚡ Performance Optimization

1. Caching

HTTP Caching Headers:

GET /kanji/:character
Response:
  Cache-Control: public, max-age=3600
  ETag: "abc123"
  Last-Modified: Mon, 01 Dec 2025 10:00:00 GMT

Next request:

GET /kanji/:character
Request:
  If-None-Match: "abc123"
  If-Modified-Since: Mon, 01 Dec 2025 10:00:00 GMT

Response:
  304 Not Modified (no body → fast!)

Redis Caching:

1. Client: GET /users/123
2. Server: Check Redis cache
   - Hit: Return cached data (fast!)
   - Miss: Query DB → Cache in Redis → Return
3. Set TTL: 1 hour

2. Pagination

Offset-based:

GET /posts?offset=20&limit=10

Pros: Đơn giản
Cons: Chậm với offset lớn

Cursor-based:

GET /posts?cursor=abc123&limit=10

Response:
{
  "data": [...],
  "nextCursor": "xyz789"
}

Pros: Performance tốt
Cons: Không thể jump to page

3. Field Selection

GET /users/123?fields=id,name,email

Chỉ return fields cần thiết
→ Giảm bandwidth
→ Faster response

4. Compression

Request:
  Accept-Encoding: gzip, deflate, br

Response:
  Content-Encoding: gzip
  [Compressed JSON]

Giảm 70-80% response size

5. Rate Limiting

Response Headers:
  X-RateLimit-Limit: 100
  X-RateLimit-Remaining: 87
  X-RateLimit-Reset: 1234567890

Nếu vượt quá:
  Status: 429 Too Many Requests
  Retry-After: 60

🎨 API Versioning

1. URL Versioning (Phổ biến nhất)

/api/v1/users
/api/v2/users
/api/v3/users

Pros:

  • Rõ ràng, dễ hiểu
  • Dễ route
  • Dễ cache

Cons:

  • URL “xấu” hơn
  • Duplicate code

2. Header Versioning

GET /users
Header: Accept: application/vnd.api+json;version=2

Pros:

  • URL “đẹp”
  • RESTful hơn

Cons:

  • Khó debug
  • Khó cache

3. Query Parameter

GET /users?version=2

Pros:

  • Dễ implement

Cons:

  • Không chuẩn
  • Khó bắt buộc version

Recommendation: URL versioning (/api/v1/) là best choice cho hầu hết cases

🛡️ Security Best Practices

1. HTTPS Only

Luôn dùng HTTPS (TLS/SSL)
HTTP → Man-in-the-middle attack

2. Input Validation

Validate mọi input:
- Type checking
- Range checking
- Format validation (email, URL, ...)
- SQL injection prevention
- XSS prevention

NestJS Example:

class CreateUserDto {
  @IsEmail()
  email: string;

  @MinLength(8)
  @Matches(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)/)
  password: string;

  @IsOptional()
  @MaxLength(50)
  displayName?: string;
}

3. Rate Limiting

Giới hạn requests per IP/user
Prevent brute force, DDoS

4. CORS Configuration

Allow-Origin: https://myapp.com
Allow-Methods: GET, POST, PUT, DELETE
Allow-Headers: Authorization, Content-Type
Allow-Credentials: true

5. Sensitive Data

❌ Never return passwords (even hashed)
❌ Never log sensitive data
✅ Mask credit cards: **** **** **** 1234
✅ Sanitize error messages (don't expose internals)

📝 Documentation

1. OpenAPI/Swagger

Auto-generate docs:

http://localhost:3000/api

Interactive API testing
Request/Response examples
Schema definitions

NestJS:

@ApiTags('users')
@Controller('users')
export class UsersController {
  @Get()
  @ApiOperation({ summary: 'Get all users' })
  @ApiResponse({ status: 200, type: [UserDto] })
  @ApiQuery({ name: 'page', required: false })
  @ApiQuery({ name: 'limit', required: false })
  findAll(@Query() query: PaginationDto) {
    return this.usersService.findAll(query);
  }
}

2. Postman Collections

Export Postman collection
Share với team
Pre-request scripts
Tests/Assertions

3. README

## API Endpoints

### Authentication
- `POST /auth/register` - Register new user
- `POST /auth/login` - Login

### Users
- `GET /users` - Get all users (Admin only)
- `GET /users/:id` - Get user by ID

## Examples

### Register User
```bash
curl -X POST http://localhost:3000/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@example.com","password":"Secure123"}'

## 🎓 Lessons Learned

### Sai lầm mình đã mắc

**1. Dùng động từ trong URL:**

❌ /getUserById/123 ❌ /createPost ✅ GET /users/123 ✅ POST /posts


**2. Không consistent:**

❌ /users vs /userList ❌ /kanji vs /kanjis ✅ Luôn dùng plural


**3. Over-nesting:**

❌ /users/123/posts/456/comments/789/likes ✅ /comments/789/likes (direct access)


**4. Không handle errors đúng:**

❌ Return 200 OK với error message ✅ Return đúng status code


**5. Trả về toàn bộ object:**

❌ GET /users → Return user với password hash ✅ Dùng DTOs để filter fields


### Best Practices tổng hợp

✅ **Keep it simple** - KISS principle  
✅ **Be consistent** - Naming, format, structure  
✅ **Document everything** - Swagger, README, examples  
✅ **Version your API** - /api/v1/  
✅ **Use proper status codes** - 200, 201, 400, 404, 500  
✅ **Validate input** - Never trust client  
✅ **Paginate large datasets** - offset/cursor pagination  
✅ **Use DTOs** - Data Transfer Objects  
✅ **Log requests** - Debug, monitoring, analytics  
✅ **Test thoroughly** - Unit, integration, E2E tests  

## 🚀 Tools & Technologies

**Backend Frameworks:**
- **NestJS** (TypeScript) - Mình dùng cho Kanji Web
- **Express** (Node.js) - Lightweight, flexible
- **FastAPI** (Python) - Fast, modern
- **Spring Boot** (Java) - Enterprise-grade
- **ASP.NET Core** (C#) - Microsoft ecosystem

**Documentation:**
- **Swagger/OpenAPI** - Auto-generate docs
- **Postman** - API testing & docs
- **Insomnia** - Alternative to Postman

**Testing:**
- **Jest** - Unit testing (NestJS default)
- **Supertest** - E2E API testing
- **Postman** - Manual testing

**Monitoring:**
- **Sentry** - Error tracking
- **Datadog** - APM monitoring
- **ELK Stack** - Logging

## 🎯 Kết luận

RESTful API design là **art + science**. Không có một "cách đúng" duy nhất, nhưng có những best practices đã được kiểm chứng qua thời gian.

**Key Takeaways:**

1. **Think from user's perspective** - API phải easy to use
2. **Consistency is key** - Nhất quán trong naming, structure, error handling
3. **Document properly** - Good docs = happy developers
4. **Plan for scale** - Pagination, caching, rate limiting từ đầu
5. **Security first** - Validation, authentication, HTTPS

**Trong dự án Kanji Web:**
- 50+ endpoints
- Swagger documentation
- JWT authentication
- Input validation với class-validator
- Pagination cho large datasets
- Error handling với global filters

Từ chỗ messy ban đầu, sau vài lần refactor, giờ API của mình đã clean, consistent và dễ maintain!

---

**Resources recommend:**

📚 **Books:**
- RESTful Web APIs (Richardson, Ruby)
- API Design Patterns (Geewax)

🌐 **Reading:**
- Microsoft REST API Guidelines
- Google API Design Guide
- Zalando RESTful API Guidelines

🛠️ **Practice:**
- Build a CRUD API
- Integrate with third-party APIs
- Read popular APIs (GitHub, Stripe, Twilio)

---

*Hi vọng bài viết giúp ích cho các bạn đang học backend development! Nếu có câu hỏi hay muốn share kinh nghiệm, comment bên dưới nhé!* 🚀

**#RESTfulAPI #APIDesign #Backend #WebDevelopment #BestPractices**