API Design
REST-first, OpenAPI-driven, backward-compatible by default.
REST Principles
Resources as nouns. HTTP methods as verbs:
GET /users # List users GET /users/123 # Get user POST /users # Create user PUT /users/123 # Replace user PATCH /users/123 # Update user DELETE /users/123 # Delete user
Relationships through URL hierarchy
GET /users/123/orders
Never: /getUser , /createOrder , /api/processPayment
HTTP Status Codes
Code When
200 Successful GET, PUT, PATCH
201 Successful POST (created)
204 Successful DELETE (no body)
400 Invalid request format
401 Not authenticated
403 Not authorized
404 Resource not found
409 Business logic conflict
422 Validation failed
500 Server error
Error Format
{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request data", "details": [ { "field": "email", "message": "Invalid email format", "value": "not-an-email" } ] } }
Pagination
GET /users?page=2&per_page=50&sort=created_at&order=desc
{ "data": [...], "meta": { "page": 2, "per_page": 50, "total": 150, "total_pages": 3 } }
Versioning
URL versioning for public APIs:
/v1/users /v2/users
Rules:
-
Major version for breaking changes only
-
6-month deprecation notice minimum
-
Side-by-side version support during transition
-
Additive changes don't require new version
OpenAPI First
Write spec before code:
openapi: 3.0.0 paths: /users/{id}: get: summary: Get user by ID parameters: - name: id in: path required: true schema: type: string responses: '200': description: User found content: application/json: schema: $ref: '#/components/schemas/User' '404': description: User not found
Anti-Patterns
-
RPC-style endpoints (/api/getUserById )
-
POST for everything
-
Deep nesting (/users/123/orders/456/items/789 )
-
Inconsistent error formats
-
Breaking changes without version bump
-
Documentation that doesn't match implementation