Back to Blog
RESTAPI DesignBest PracticesHTTP

REST API Design Principles That Stand the Test of Time

Design RESTful APIs that developers love. From resource naming to error handling to versioning strategies.

B
Bootspring Team
Engineering
October 28, 2024
4 min read

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/123

HTTP 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=10

Request/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: 1640000000

Versioning#

GET /v1/users GET /v2/users

Header Versioning#

GET /users Accept: application/vnd.myapi.v1+json

HATEOAS (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.

Share this article

Help spread the word about Bootspring