A well-designed REST API is intuitive, consistent, and a pleasure to use. Here are principles that lead to APIs developers love.
Resource Naming#
Use Nouns, Not Verbs#
❌ Bad
GET /getUsers
POST /createUser
PUT /updateUser/123
DELETE /deleteUser/123
✅ Good
GET /users
POST /users
PUT /users/123
DELETE /users/123
Use Plural Nouns#
❌ Inconsistent
GET /user/123
GET /users
✅ Consistent
GET /users/123
GET /users
Nest Resources Logically#
# User's posts
GET /users/123/posts
POST /users/123/posts
# Post's comments
GET /posts/456/comments
POST /posts/456/comments
# Avoid deep nesting (max 2 levels)
❌ /users/123/posts/456/comments/789/replies
✅ /comments/789/replies
HTTP Methods#
Method | Usage | Idempotent | Safe
---------+--------------------------+------------+------
GET | Retrieve resource(s) | Yes | Yes
POST | Create resource | No | No
PUT | Replace resource | Yes | No
PATCH | Partial update | No* | No
DELETE | Remove resource | Yes | No
* PATCH can be idempotent depending on implementation
Examples#
1# Get all users
2GET /users
3
4# Get single user
5GET /users/123
6
7# Create user
8POST /users
9Content-Type: application/json
10
11{"name": "John", "email": "john@example.com"}
12
13# Replace user (full update)
14PUT /users/123
15Content-Type: application/json
16
17{"name": "John Doe", "email": "johndoe@example.com"}
18
19# Partial update
20PATCH /users/123
21Content-Type: application/json
22
23{"name": "John Doe"}
24
25# Delete user
26DELETE /users/123HTTP Status Codes#
Success Codes#
200 OK - General success
201 Created - Resource created (POST)
204 No Content - Success, no body (DELETE)
Client Error Codes#
400 Bad Request - Invalid input
401 Unauthorized - No/invalid authentication
403 Forbidden - Authenticated but not allowed
404 Not Found - Resource doesn't exist
409 Conflict - Resource conflict
422 Unprocessable - Validation failed
429 Too Many Reqs - Rate limited
Server Error Codes#
500 Internal Error - Server error
502 Bad Gateway - Upstream error
503 Unavailable - Temporarily unavailable
504 Gateway Timeout - Upstream timeout
Error Responses#
Consistent Structure#
1{
2 "error": {
3 "code": "VALIDATION_ERROR",
4 "message": "Validation failed",
5 "details": [
6 {
7 "field": "email",
8 "message": "Invalid email format"
9 },
10 {
11 "field": "age",
12 "message": "Must be a positive number"
13 }
14 ]
15 }
16}Implementation#
1class ApiError extends Error {
2 constructor(
3 public statusCode: number,
4 public code: string,
5 message: string,
6 public details?: unknown[]
7 ) {
8 super(message);
9 }
10
11 toJSON() {
12 return {
13 error: {
14 code: this.code,
15 message: this.message,
16 details: this.details,
17 },
18 };
19 }
20}
21
22// Usage
23throw new ApiError(400, 'VALIDATION_ERROR', 'Validation failed', [
24 { field: 'email', message: 'Invalid email format' },
25]);Pagination#
Offset Pagination#
1GET /users?offset=20&limit=10
2
3Response:
4{
5 "data": [...],
6 "pagination": {
7 "total": 100,
8 "offset": 20,
9 "limit": 10
10 }
11}Cursor Pagination (Preferred)#
1GET /users?cursor=abc123&limit=10
2
3Response:
4{
5 "data": [...],
6 "pagination": {
7 "nextCursor": "def456",
8 "hasMore": true
9 }
10}Filtering, Sorting, and Fields#
1# Filtering
2GET /users?status=active&role=admin
3
4# Sorting
5GET /users?sort=created_at:desc,name:asc
6
7# Field selection
8GET /users?fields=id,name,email
9
10# Combined
11GET /users?status=active&sort=name:asc&fields=id,name&limit=10Request/Response Design#
Consistent Envelopes#
1// Single resource
2{
3 "data": {
4 "id": "123",
5 "name": "John"
6 }
7}
8
9// Collection
10{
11 "data": [
12 {"id": "123", "name": "John"},
13 {"id": "456", "name": "Jane"}
14 ],
15 "pagination": {
16 "total": 100,
17 "nextCursor": "abc"
18 }
19}Use Camel Case#
{
"firstName": "John",
"lastName": "Doe",
"createdAt": "2024-01-15T10:30:00Z"
}ISO 8601 for Dates#
{
"createdAt": "2024-01-15T10:30:00Z",
"scheduledFor": "2024-02-01T09:00:00-05:00"
}Headers#
Request Headers#
Content-Type: application/json
Accept: application/json
Authorization: Bearer <token>
X-Request-ID: <uuid>Response Headers#
Content-Type: application/json
X-Request-ID: <uuid>
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000Versioning#
URL Versioning (Recommended)#
GET /v1/users
GET /v2/users
Header Versioning#
GET /users
Accept: application/vnd.myapi.v1+jsonHATEOAS (Optional)#
1{
2 "data": {
3 "id": "123",
4 "name": "John",
5 "email": "john@example.com"
6 },
7 "links": {
8 "self": "/users/123",
9 "posts": "/users/123/posts",
10 "followers": "/users/123/followers"
11 }
12}Documentation#
1# OpenAPI/Swagger
2openapi: 3.0.0
3info:
4 title: My API
5 version: 1.0.0
6
7paths:
8 /users:
9 get:
10 summary: List users
11 parameters:
12 - name: status
13 in: query
14 schema:
15 type: string
16 enum: [active, inactive]
17 responses:
18 200:
19 description: Success
20 content:
21 application/json:
22 schema:
23 $ref: '#/components/schemas/UserList'Conclusion#
Good REST API design is about consistency and predictability. Follow conventions, use proper HTTP methods and status codes, and document everything.
The best API is one where developers can guess the correct endpoint without reading documentation. Achieve that through consistent patterns applied throughout.