4.9 KiB
4.9 KiB
API Implementation Patterns
Reference guide for implementing REST, GraphQL, and tRPC APIs. Use this when implementing API designs.
REST API Implementation
Resource Naming
- Use nouns, not verbs (
/usersnot/getUsers) - Plural for collections (
/usersnot/user) - Hierarchical for relationships (
/users/123/posts) - kebab-case for multi-word resources (
/blog-posts)
HTTP Methods
- GET: Retrieve (safe, idempotent)
- POST: Create (not idempotent)
- PUT: Replace entire resource (idempotent)
- PATCH: Partial update (idempotent)
- DELETE: Remove (idempotent)
Status Codes
- 2xx Success: 200 OK, 201 Created, 204 No Content
- 4xx Client Errors: 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
- 5xx Server Errors: 500 Internal Server Error, 503 Service Unavailable
Versioning
- URL path:
/api/v1/users(recommended - simple, explicit) - Header:
Accept: application/vnd.api.v1+json(cleaner URLs) - Query param:
/api/users?version=1(not recommended - breaks caching)
Pagination
- Offset-based:
/users?page=1&limit=20(simple, familiar) - Cursor-based:
/users?cursor=abc123&limit=20(consistent, handles real-time data) - Always include: total count, has_next, has_prev
Filtering & Sorting
- Query params:
/users?role=admin&status=active&sort=-createdAt - Use
-prefix for descending sort - Support multiple filters with AND logic
- Document available filters in API docs
Error Responses
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{"field": "email", "message": "Invalid email format"}
]
}
}
GraphQL Implementation
Schema Design
- Define types and relationships clearly
- Use enums for fixed values
- Mark required fields as non-null
- Input types for mutations
- Cursor-based pagination (connections)
Resolver Best Practices
- Implement DataLoader to avoid N+1 queries
- Field-level resolvers for computed properties
- Structured error handling
- Authentication at resolver level
- Use context for request-scoped data
Performance
- Query depth limiting (prevent deep nesting abuse)
- Query complexity analysis (cost-based)
- Persisted queries for production
- Caching with Apollo or similar
- Batching with DataLoader
Error Handling
type Error {
message: String!
code: String!
path: [String!]
}
type UserResult {
user: User
errors: [Error!]
}
tRPC Implementation
Procedures
import { z } from 'zod';
const userRouter = router({
getById: publicProcedure
.input(z.string())
.query(({ input }) => db.user.findUnique({ where: { id: input } })),
create: protectedProcedure
.input(z.object({
email: z.string().email(),
name: z.string().min(2)
}))
.mutation(({ input }) => db.user.create({ data: input })),
});
Middleware
- Auth middleware for protected procedures
- Logging middleware for all procedures
- Error handling middleware
- Context builders for request-scoped data
Router Organization
- Separate routers by domain (user, post, comment)
- Merge at app level
- Share middleware across routers
- Type-safe throughout
WebSocket Patterns
Event Structure
// Server → Client
{
event: 'message',
data: { userId: '123', text: 'Hello' },
timestamp: 1234567890
}
// Client → Server
{
action: 'send_message',
payload: { text: 'Hello' }
}
Connection Management
- Authenticate on connection
- Heartbeat/ping-pong for keepalive
- Reconnection logic with exponential backoff
- Room/channel management for broadcasts
Error Handling
- Send error events to client
- Graceful degradation (fallback to polling)
- Connection state management
Rate Limiting
Implementation
- Token bucket algorithm (smooth traffic)
- Fixed window (simple, good enough)
- Sliding window (accurate, complex)
Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1234567890
Retry-After: 60
Response
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests",
"retryAfter": 60
}
}
Caching
HTTP Caching
Cache-Control: public, max-age=3600
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
Conditional Requests
If-None-Match: "abc123"→ 304 Not ModifiedIf-Modified-Since: ...→ 304 Not Modified
Cache Invalidation
- Time-based (TTL)
- Event-based (on mutations)
- Tag-based (grouped invalidation)
Documentation
OpenAPI/Swagger
- Define all endpoints, parameters, responses
- Include examples for each
- Document authentication
- Use tags for grouping
- Validate spec before deploying
GraphQL SDL
- Add descriptions to all types and fields
- Document deprecations with @deprecated
- Provide usage examples in comments
- Auto-generate docs from schema