REST API Core Knowledge
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: rest-api for comprehensive documentation.
Resource Naming
Collections (plural nouns)
GET /users # List users POST /users # Create user GET /users/{id} # Get user PUT /users/{id} # Update user DELETE /users/{id} # Delete user
Nested resources
GET /users/{id}/posts # User's posts POST /users/{id}/posts # Create user's post
Avoid verbs in URLs
❌ GET /getUsers ❌ POST /createUser ✅ GET /users ✅ POST /users
HTTP Methods
Method Purpose Idempotent
GET Read resource Yes
POST Create resource No
PUT Replace resource Yes
PATCH Partial update Yes
DELETE Remove resource Yes
Status Codes
Code Meaning Use Case
200 OK Successful GET/PUT/PATCH
201 Created Successful POST
204 No Content Successful DELETE
400 Bad Request Validation error
401 Unauthorized Missing/invalid auth
403 Forbidden No permission
404 Not Found Resource doesn't exist
409 Conflict Duplicate/conflict
422 Unprocessable Semantic error
500 Server Error Internal error
Response Format
// Success { "data": { "id": 1, "name": "John" }, "meta": { "timestamp": "2024-01-15T10:00:00Z" } }
// Collection { "data": [...], "meta": { "total": 100, "page": 1, "limit": 20 } }
// Error { "error": { "code": "VALIDATION_ERROR", "message": "Email is required", "details": [{ "field": "email", "message": "Required" }] } }
Query Parameters
GET /users?status=active&sort=-createdAt&page=1&limit=20 GET /users?fields=id,name,email GET /users?include=posts,profile GET /users?filter[age][gte]=18
When NOT to Use This Skill
-
GraphQL API design (use graphql skill)
-
tRPC type-safe APIs (use trpc skill)
-
OpenAPI specification writing (use openapi skill)
-
Real-time APIs requiring WebSockets or SSE
-
APIs requiring complex nested queries (consider GraphQL)
Anti-Patterns
Anti-Pattern Why It's Bad Solution
Using verbs in URLs (/getUser , /createOrder ) Not RESTful, violates resource naming Use HTTP methods on nouns (GET /users , POST /orders )
Returning 200 for errors Misleading, breaks HTTP semantics Use appropriate 4xx/5xx status codes
Not versioning API Breaking changes affect all clients Use URL or header versioning (/v1/ , /v2/ )
Exposing database IDs directly Security risk, implementation leak Use UUIDs or opaque identifiers
No pagination on large collections Performance issues, timeouts Implement cursor or offset pagination
Ignoring HTTP caching headers Poor performance, unnecessary load Use ETag, Cache-Control, Last-Modified
Using GET for state-changing operations Security risk, breaks REST principles Use POST, PUT, PATCH, DELETE
Inconsistent response formats Client confusion, integration issues Standardize on JSON envelope format
Missing rate limiting API abuse, resource exhaustion Implement rate limiting with 429 responses
Quick Troubleshooting
Issue Possible Cause Solution
401 Unauthorized Missing or invalid auth token Check Authorization header, verify token
403 Forbidden Valid auth but insufficient permissions Check user roles, verify access control
404 Not Found Resource doesn't exist or wrong path Verify endpoint URL, check resource ID
409 Conflict Resource already exists or state conflict Check uniqueness constraints, handle idempotency
422 Unprocessable Entity Validation failed Check request body, validate against schema
429 Too Many Requests Rate limit exceeded Implement backoff, check rate limit headers
500 Internal Server Error Server-side bug or crash Check server logs, add error handling
CORS errors Missing CORS headers Configure CORS middleware with allowed origins
Slow API responses No pagination, N+1 queries Add pagination, optimize database queries
Production Readiness
Security Configuration
// Input validation and sanitization import { z } from 'zod';
const CreateUserSchema = z.object({ name: z.string().min(1).max(100).trim(), email: z.string().email().toLowerCase(), age: z.number().int().min(0).max(150).optional(), });
// Validate request body app.post('/users', async (req, res) => { const result = CreateUserSchema.safeParse(req.body); if (!result.success) { return res.status(400).json({ error: { code: 'VALIDATION_ERROR', details: result.error.issues }, }); } // Use result.data (validated and transformed) });
// SQL injection prevention - always use parameterized queries
// NEVER: SELECT * FROM users WHERE id = '${userId}'
// GOOD: Use ORM or prepared statements
Rate Limiting
import rateLimit from 'express-rate-limit';
// Different limits for different endpoints const standardLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, standardHeaders: true, message: { error: { code: 'RATE_LIMIT_EXCEEDED' } }, });
const authLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 5, // Strict for auth endpoints skipSuccessfulRequests: true, });
app.use('/api', standardLimiter); app.use('/api/auth/login', authLimiter);
API Versioning
// URL versioning (recommended for breaking changes) app.use('/api/v1', v1Router); app.use('/api/v2', v2Router);
// Header versioning (for minor versions) app.use('/api', (req, res, next) => { const version = req.headers['api-version'] || '1.0'; req.apiVersion = version; next(); });
// Sunset header for deprecated endpoints app.get('/api/v1/legacy', (req, res) => { res.set('Sunset', 'Sat, 01 Jun 2025 00:00:00 GMT'); res.set('Deprecation', 'true'); // ... handle request });
Pagination
// Cursor-based pagination (recommended for large datasets) interface PaginatedResponse<T> { data: T[]; meta: { nextCursor: string | null; hasMore: boolean; }; }
app.get('/users', async (req, res) => { const cursor = req.query.cursor as string | undefined; const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const users = await db.users.findMany({ take: limit + 1, // Fetch one extra to check if there's more cursor: cursor ? { id: cursor } : undefined, orderBy: { createdAt: 'desc' }, });
const hasMore = users.length > limit; const data = hasMore ? users.slice(0, -1) : users;
res.json({ data, meta: { nextCursor: hasMore ? data[data.length - 1].id : null, hasMore, }, }); });
Error Handling
// Consistent error response format interface APIError { error: { code: string; message: string; details?: unknown; requestId?: string; }; }
// Global error handler app.use((err: Error, req: Request, res: Response, next: NextFunction) => { const requestId = req.headers['x-request-id'] as string;
// Log error with context logger.error({ err, requestId, path: req.path, method: req.method, });
// Don't leak internal errors const statusCode = err instanceof HTTPError ? err.statusCode : 500; const message = statusCode === 500 ? 'Internal Server Error' : err.message;
res.status(statusCode).json({ error: { code: err.code || 'INTERNAL_ERROR', message, requestId, }, }); });
Monitoring Metrics
Metric Alert Threshold
Request latency p99
500ms
Error rate (4xx)
5%
Error rate (5xx)
1%
Rate limit hits
100/min
Request size
10MB
CORS Configuration
import cors from 'cors';
const corsOptions = { origin: (origin, callback) => { const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || []; if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'], credentials: true, maxAge: 86400, // 24 hours };
app.use(cors(corsOptions));
Request/Response Logging
import pino from 'pino-http';
app.use(pino({ redact: ['req.headers.authorization', 'req.body.password'], serializers: { req: (req) => ({ method: req.method, url: req.url, query: req.query, }), res: (res) => ({ statusCode: res.statusCode, }), }, }));
Checklist
-
Input validation on all endpoints
-
Rate limiting configured
-
CORS properly restricted
-
API versioning strategy
-
Cursor-based pagination
-
Consistent error format
-
Request ID tracking
-
Structured logging
-
No sensitive data in URLs
-
Body size limits configured
-
Timeout handling
-
Health check endpoint
Reference Documentation
-
Pagination
-
Versioning