// Provides REST and GraphQL API design patterns for Node.js, Flask, and FastAPI. Use when designing endpoints, request/response structures, API architecture, pagination, authentication, rate limiting, or when working in /api/ or /routes/ directories.
| name | api-design-patterns |
| description | Provides REST and GraphQL API design patterns for Node.js, Flask, and FastAPI. Use when designing endpoints, request/response structures, API architecture, pagination, authentication, rate limiting, or when working in /api/ or /routes/ directories. |
Best practices for designing RESTful and GraphQL APIs.
For detailed code examples, see:
references/express-examples.md - Node.js + Express patternsreferences/fastapi-examples.md - FastAPI (Python) patternsreferences/flask-examples.md - Flask (Python) patternsGET /api/v1/users # List users
GET /api/v1/users/:id # Get specific user
POST /api/v1/users # Create user
PUT /api/v1/users/:id # Update user (full)
PATCH /api/v1/users/:id # Update user (partial)
DELETE /api/v1/users/:id # Delete user
GET /api/v1/users/:id/posts # Nested resource
Avoid:
/getUsers)/user/create)| Code | Meaning | When to Use |
|---|---|---|
| 200 | OK | Success (GET, PUT, PATCH) |
| 201 | Created | Resource created (POST) |
| 204 | No Content | Success with no body (DELETE) |
| 400 | Bad Request | Invalid input |
| 401 | Unauthorized | Not authenticated |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate or state conflict |
| 422 | Unprocessable | Validation failed |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Error | Server error |
{
"data": {
"id": "123",
"name": "John Doe"
},
"meta": {
"timestamp": "2025-10-26T10:00:00Z"
}
}
{
"data": [
{ "id": "1", "name": "User 1" }
],
"meta": {
"total": 100,
"page": 1,
"perPage": 20,
"totalPages": 5
},
"links": {
"first": "/api/v1/users?page=1",
"prev": null,
"next": "/api/v1/users?page=2",
"last": "/api/v1/users?page=5"
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
}
// URL versioning (recommended)
app.use('/api/v1', routesV1);
app.use('/api/v2', routesV2);
// Deprecation headers
res.setHeader('Deprecation', 'true');
res.setHeader('Sunset', 'Wed, 11 Nov 2025 11:11:11 GMT');
type User {
id: ID!
name: String!
email: String!
posts: [Post!]! # Resolver handles N+1 with DataLoader
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users(first: Int, after: String): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
input CreateUserInput {
name: String!
email: String!
}
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return dataSources.usersAPI.getUser(id);
},
users: async (_, { first, after }, { dataSources }) => {
return dataSources.usersAPI.getUsers({ first, after });
},
},
Mutation: {
createUser: async (_, { input }, { dataSources }) => {
return dataSources.usersAPI.createUser(input);
},
},
User: {
// Field resolver with DataLoader to prevent N+1
posts: async (user, _, { loaders }) => {
return loaders.postsByUserId.load(user.id);
},
},
};
// Throw typed errors
import { GraphQLError } from 'graphql';
throw new GraphQLError('User not found', {
extensions: {
code: 'NOT_FOUND',
http: { status: 404 },
},
});
// Common error codes
// UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, INTERNAL_ERROR
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
| Use GraphQL | Use REST |
|---|---|
| Multiple clients with different data needs | Simple CRUD operations |
| Deeply nested data in single request | Caching critical (HTTP caching) |
| Rapid iteration, evolving schema | Public API with stability guarantees |
| Mobile apps (minimize requests) | File uploads, streaming |
/users)Always validate using schemas:
TypeScript (Zod):
const createUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
});
Python (Pydantic):
class UserCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
email: EmailStr
age: Optional[int] = Field(None, ge=0, le=150)
Authorization: Bearer <token> headerconst limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // requests per window
message: {
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests',
},
},
});
describe('GET /api/v1/users', () => {
it('returns paginated users', async () => {
const response = await request(app)
.get('/api/v1/users?page=1')
.expect(200);
expect(response.body).toHaveProperty('data');
expect(response.body).toHaveProperty('meta');
});
it('returns 401 without auth', async () => {
await request(app).get('/api/v1/users').expect(401);
});
});
Good API design makes your API intuitive, consistent, and easy to use.