REST API Design

What is it: REST (Representational State Transfer) is an architectural style for APIs. It uses HTTP methods as the actions and URLs as the resources. Resources are nouns (not verbs).

Core constraints: - Stateless: no session on the server; each request is self-contained - Resource-based URLs: /users/123 not /getUser?id=123 - HTTP method semantics: GET to read, POST to create, PUT/PATCH to update, DELETE to remove - Uniform interface: consistent behavior across all resources

URL design: Use plural nouns for collections: /users, /orders. Use sub-resources for relationships: /users/123/orders (orders belonging to user 123). Avoid verbs in URLs: /users/123/activate is acceptable for non-CRUD operations but prefer /users/123/status with PATCH.

Status codes: - 200 OK: success with body - 201 Created: resource created (include Location: /users/456 header) - 204 No Content: success with no body (DELETE) - 400 Bad Request: client sent invalid data (validation error) - 401 Unauthorized: not authenticated (no token or invalid token) - 403 Forbidden: authenticated but not authorized (insufficient permissions) - 404 Not Found: resource does not exist - 409 Conflict: state conflict (duplicate email, optimistic lock failure) - 422 Unprocessable Entity: semantic validation failure (valid JSON but business rule violated) - 429 Too Many Requests: rate limit exceeded - 500 Internal Server Error: unexpected server error

PUT vs PATCH: PUT replaces the entire resource: you must send all fields. If you omit email, it is cleared. Safer for full object replacement. PATCH sends only the changed fields — more efficient for large objects with one field changing. PATCH is preferred in practice because clients rarely want to clear unmentioned fields.

Versioning: URL versioning (/v1/users) is most common — explicit, easy to route in reverse proxies, easy to test. Header versioning (Accept: application/vnd.myapi.v1+json) keeps URLs clean but is harder to test in browsers. Bump the version when making breaking changes (removing fields, changing types, changing semantics). Maintain old versions for a deprecation window.

Designing a user service API:

GET    /users          → list users (with pagination: ?page=1&limit=20 or ?cursor=xxx)
POST   /users          → create user (body: {name, email, password})
GET    /users/:id      → get user by ID
PUT    /users/:id      → replace user
PATCH  /users/:id      → partial update
DELETE /users/:id      → delete user

GET    /users/:id/orders   → list user's orders
POST   /users/:id/orders   → create order for user

Real-world usage: Proxel's internal API uses REST for job submission and status polling. gRPC-wallet uses gRPC (not REST) for internal service calls — binary, streaming, strongly typed — but exposes a REST gateway for external clients.

Common pitfall

Returning 200 OK for every response and encoding the real result in the response body ({"success": false, "error": "not found"}) throws away everything HTTP status codes exist to communicate — caches, proxies, monitoring tools, and generic HTTP clients all key off the status code, not the body's shape, which they don't know how to parse generically. A 404 in the body with a 200 status looks like a successful request to anything that isn't specifically your own frontend's error-handling code, including uptime monitors and CDN caching rules that would otherwise correctly treat a 4xx/5xx differently from a real success.