api-design-restful
Scannednpx machina-cli add skill autohandai/community-skills/api-design-restful --openclawFiles (1)
SKILL.md
5.6 KB
RESTful API Design
Core Principles
- Resource-Oriented - URLs represent nouns, not verbs
- Stateless - Each request contains all necessary information
- Consistent - Use standard HTTP methods and status codes
- Versioned - Support API evolution without breaking clients
URL Structure
# Collection resources
GET /api/v1/users # List users
POST /api/v1/users # Create user
# Individual resources
GET /api/v1/users/:id # Get user
PUT /api/v1/users/:id # Replace user
PATCH /api/v1/users/:id # Update user
DELETE /api/v1/users/:id # Delete user
# Nested resources
GET /api/v1/users/:id/posts # User's posts
POST /api/v1/users/:id/posts # Create post for user
# Filtering, sorting, pagination
GET /api/v1/users?status=active&sort=-createdAt&page=2&limit=20
Response Structure
Success Response
interface SuccessResponse<T> {
success: true;
data: T;
meta?: {
page?: number;
limit?: number;
total?: number;
totalPages?: number;
};
}
// Example
{
"success": true,
"data": { "id": "123", "name": "John" },
"meta": { "requestId": "abc-123" }
}
Error Response
interface ErrorResponse {
success: false;
error: {
code: string; // Machine-readable code
message: string; // Human-readable message
details?: unknown; // Field-level errors
};
}
// Example
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": {
"email": "Invalid email format",
"age": "Must be a positive number"
}
}
}
HTTP Status Codes
// Success
200 OK // Successful GET, PUT, PATCH
201 Created // Successful POST
204 No Content // Successful DELETE
// Client Errors
400 Bad Request // Validation errors
401 Unauthorized // Missing/invalid auth
403 Forbidden // Insufficient permissions
404 Not Found // Resource doesn't exist
409 Conflict // Duplicate/state conflict
422 Unprocessable // Semantic errors
429 Too Many Reqs // Rate limited
// Server Errors
500 Internal Error // Unexpected server error
503 Unavailable // Service temporarily down
Express.js Implementation
import express from 'express';
const app = express();
// Async handler wrapper
const asyncHandler = (fn: RequestHandler) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// Controller
const getUsers = asyncHandler(async (req, res) => {
const { page = 1, limit = 20, status } = req.query;
const { users, total } = await userService.findAll({ page, limit, status });
res.json({
success: true,
data: users,
meta: { page, limit, total, totalPages: Math.ceil(total / limit) }
});
});
// Error handler middleware
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
success: false,
error: {
code: err.code || 'INTERNAL_ERROR',
message: err.message || 'Something went wrong',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
});
Validation with Zod
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
role: z.enum(['user', 'admin']).default('user'),
});
// Middleware
const validate = (schema: z.ZodSchema) => (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid request body',
details: result.error.flatten().fieldErrors
}
});
}
req.body = result.data;
next();
};
app.post('/users', validate(createUserSchema), createUser);
Authentication
// JWT middleware
const authenticate = asyncHandler(async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new ApiError(401, 'UNAUTHORIZED', 'Missing auth token');
}
const payload = await verifyToken(token);
req.user = payload;
next();
});
// Role-based authorization
const authorize = (...roles: string[]) => (req, res, next) => {
if (!roles.includes(req.user.role)) {
throw new ApiError(403, 'FORBIDDEN', 'Insufficient permissions');
}
next();
};
app.delete('/users/:id', authenticate, authorize('admin'), deleteUser);
Rate Limiting
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
res.status(429).json({
success: false,
error: {
code: 'RATE_LIMITED',
message: 'Too many requests, please try again later'
}
});
}
});
app.use('/api/', limiter);
Best Practices
- Use plural nouns for resources (
/usersnot/user) - Version your API from day one (
/api/v1/) - Return appropriate status codes for every response
- Include request IDs in responses for debugging
- Document with OpenAPI/Swagger specification
- Implement pagination for list endpoints
- Use consistent error format across all endpoints
- Log all requests with correlation IDs
Source
git clone https://github.com/autohandai/community-skills/blob/main/api-design-restful/SKILL.mdView on GitHub Overview
Defines RESTful API design patterns with resource-oriented URLs, stateless requests, and versioning. It emphasizes consistent HTTP methods and codes, a predictable response structure, and robust error handling to enable clear documentation and reliable client interactions.
How This Skill Works
Design endpoints under a versioned path like /api/v1, using nouns for resources. Responses follow a consistent SuccessResponse with data and optional meta, or an ErrorResponse with a machine code and human message; errors map to standard HTTP status codes. Implement with an Express.js pattern using async handlers, centralized error middleware, and Zod-based validation to ensure request bodies meet schema.
When to Use It
- When designing a new API from scratch with clear resource boundaries (users, posts, etc.)
- When you need predictable, machine-readable success and error responses for clients
- When implementing nested resources and support for filtering, sorting, and pagination in URLs
- When evolving the API while preserving compatibility through versioning
- When you want to enforce schema validation with a library like Zod and standardized error handling
Quick Start
- Step 1: Define resource endpoints under a versioned base path like /api/v1
- Step 2: Implement controllers to return SuccessResponse or ErrorResponse and add an Express error middleware
- Step 3: Add Zod schemas and a validation middleware, then test with curl or Postman
Best Practices
- Favor resource-oriented URLs that use nouns and avoid verbs in paths
- Use standard HTTP methods and codes consistently (200/201/204 for success; 400/401/403/404/409/422/429 for client errors; 500/503 for server errors)
- Provide a predictable response structure (SuccessResponse or ErrorResponse) with clear codes and messages
- Document endpoints, query params, and response shapes to enable client adoption
- Validate inputs with schemas (e.g., Zod) and centralize error handling
Example Use Cases
- GET /api/v1/users returns a paginated list with success true and data
- POST /api/v1/users creates a user and returns 201 with the new user and meta including total pages
- GET /api/v1/users/:id that does not exist returns 404 with ErrorResponse code NOT_FOUND
- GET /api/v1/users/:id/posts shows a nested resource under a user
- Validation failure using Zod returns 400 with VALIDATION_ERROR and details
Frequently Asked Questions
Add this skill to your agents