| name | api-patterns |
| description | REST API best practices including request validation, error handling, authentication, rate limiting, and documentation. Use when building backend APIs. |
API Patterns Skill
Quick Reference
Use when: Building REST APIs, implementing authentication, handling errors, validating inputs
Key Patterns:
- Request validation
- Standardized error responses
- Authentication middleware
- Rate limiting
- Pagination
- API documentation
1. Request Validation
Always validate incoming requests at the boundary.
Pattern with schema validation:
const CreateUserSchema = {
email: 'string, email format, required',
password: 'string, min 12 chars, required',
name: 'string, 2-100 chars, required',
age: 'number, integer, min 18, optional'
};
function createUserHandler(req, res) {
const validation = validateSchema(CreateUserSchema, req.body);
if (!validation.success) {
return res.status(400).json({
error: 'Validation failed',
details: validation.errors
});
}
const user = await createUser(validation.data);
res.status(201).json(user);
}
Key Points:
- Validate at API boundary, not deep in business logic
- Return specific error messages
- Use 400 status code for validation failures
2. Standardized Error Responses
Use consistent error format across all endpoints.
interface ErrorResponse {
error: string;
code?: string;
details?: unknown;
}
class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code?: string,
public details?: unknown
) {
super(message);
}
}
throw new AppError('User not found', 404, 'USER_NOT_FOUND');
throw new AppError('Validation failed', 400, 'VALIDATION_ERROR', errors);
Error Handler Pattern:
function errorHandler(err, req, res, next) {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: err.message,
code: err.code,
details: err.details
});
}
console.error('Unexpected error:', err);
res.status(500).json({
error: 'Internal server error'
});
}
3. Authentication Middleware
Protect routes with authentication middleware.
function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new AppError('Authentication required', 401, 'NO_TOKEN');
}
try {
const decoded = verifyToken(token);
req.userId = decoded.userId;
next();
} catch (err) {
throw new AppError('Invalid token', 401, 'INVALID_TOKEN');
}
}
function optionalAuth(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (token) {
try {
const decoded = verifyToken(token);
req.userId = decoded.userId;
} catch {
}
}
next();
}
router.get('/api/profile', requireAuth, getProfileHandler);
router.get('/api/posts', optionalAuth, getPostsHandler);
4. Rate Limiting
Protect endpoints from abuse.
const generalLimiter = {
windowMs: 15 * 60 * 1000,
max: 100
};
const authLimiter = {
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true
};
app.use('/api/', rateLimitMiddleware(generalLimiter));
router.post('/api/auth/login', rateLimitMiddleware(authLimiter), loginHandler);
5. Pagination
Standard pagination for list endpoints.
const DEFAULT_PAGE = 1;
const DEFAULT_LIMIT = 20;
const MAX_LIMIT = 100;
function parsePagination(query) {
const page = Math.max(1, parseInt(query.page) || DEFAULT_PAGE);
const limit = Math.min(MAX_LIMIT, Math.max(1, parseInt(query.limit) || DEFAULT_LIMIT));
const offset = (page - 1) * limit;
return { page, limit, offset };
}
function paginatedResponse(data, total, page, limit) {
return {
data,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page < Math.ceil(total / limit),
hasPrev: page > 1
}
};
}
async function listUsersHandler(req, res) {
const { page, limit, offset } = parsePagination(req.query);
const [users, total] = await Promise.all([
db.users.findMany({ skip: offset, take: limit }),
db.users.count()
]);
res.json(paginatedResponse(users, total, page, limit));
}
6. API Response Format
Use consistent response structure.
{
data: T,
meta?: {
pagination?: {...},
timestamp: string
}
}
{
error: string,
code?: string,
details?: unknown
}
7. HTTP Status Codes
Use appropriate status codes:
| Code | Meaning | When to Use |
|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST (resource created) |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Validation errors, malformed request |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource conflict (duplicate, etc.) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Error | Unexpected server error |
8. RESTful Resource Naming
Follow conventions:
GET /api/resources - List all (with pagination)
GET /api/resources/:id - Get one
POST /api/resources - Create new
PUT /api/resources/:id - Replace entirely
PATCH /api/resources/:id - Partial update
DELETE /api/resources/:id - Delete
Nested resources:
GET /api/users/:userId/posts - User's posts
POST /api/users/:userId/posts - Create post for user
Checklist for New Endpoints