| name | api-design |
| description | Design clean, scalable, and maintainable REST and GraphQL APIs following industry best practices. Use when designing public or internal APIs, planning endpoint structures, defining request/response contracts, establishing versioning strategies, implementing authentication patterns, designing data models, creating API documentation, ensuring consistent error handling, optimizing for performance, or establishing service contracts between microservices. |
API Design - Building Clean, Scalable REST & GraphQL APIs
When to use this skill
- Designing new REST or GraphQL APIs from scratch
- Planning endpoint structures and URL patterns
- Defining request/response contracts and data schemas
- Establishing API versioning and deprecation strategies
- Implementing authentication and authorization patterns
- Creating API documentation (OpenAPI/Swagger)
- Designing error response formats and status codes
- Planning pagination, filtering, and sorting strategies
- Establishing rate limiting and throttling policies
- Designing webhooks or event-driven integrations
- Creating service contracts for microservices
- Optimizing API performance and caching strategies
When to use this skill
- Designing public or internal APIs, planning endpoints, defining contracts between services.
- When working on related tasks or features
- During development that requires this expertise
Use when: Designing public or internal APIs, planning endpoints, defining contracts between services.
Core Principles
- Consistency - Predictable patterns across endpoints
- Simplicity - Easy to understand and use
- Versioning - Support evolution without breaking clients
- Security - Authentication, authorization, rate limiting
- Documentation - Clear, up-to-date API specs
REST API Design
1. Resource-Based URLs
✅ Good - Nouns, not verbs
GET /users - List users
GET /users/:id - Get specific user
POST /users - Create user
PUT /users/:id - Update user (full replace)
PATCH /users/:id - Update user (partial)
DELETE /users/:id - Delete user
GET /users/:id/posts - Get user's posts
POST /users/:id/posts - Create post for user
❌ Bad - Verbs in URLs
GET /getUsers
POST /createUser
POST /users/delete/:id
2. HTTP Methods & Status Codes
app.get('/users', async (req, res) => {
const users = await db.users.findAll();
res.json(users);
});
app.post('/users', async (req, res) => {
const user = await db.users.create(req.body);
res.status(201)
.location(`/users/${user.id}`)
.json(user);
});
app.put('/users/:id', async (req, res) => {
const user = await db.users.update(req.params.id, req.body);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.delete('/users/:id', async (req, res) => {
await db.users.delete(req..);
res.().();
});
- succeeded
- created
- , no response body
- input
- authenticated
- but no permission
- doesn
3. Request/Response Format
interface ApiResponse<T> {
data: T;
meta?: {
page?: number;
limit?: number;
total?: number;
};
links?: {
self: string;
next?: string;
prev?: string;
};
}
{
"data": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
}
}
{
"data": [
{ "id": "1", "name": "User 1" },
{ "id": "2", "name": "User 2" }
],
"meta": {
"page": 1,
"limit": 20,
"total": 100
},
"links": {
"self": "/users?page=1",
"next": "/users?page=2"
}
}
{
"error": {
"code": "VALIDATION_ERROR",
"message": ,
: [
{
: ,
:
}
]
}
}
4. Filtering, Sorting, Pagination
GET /users?status=active&role=admin
GET /posts?author=john&tags=tech,programming
GET /products?minPrice=10&maxPrice=100
app.get('/users', async (req, res) => {
const { status, role, page = 1, limit = 20, sort = 'createdAt' } = req.query;
const query = {};
if (status) query.status = status;
if (role) query.role = role;
const users = await db.users.findMany({
where: query,
skip: (page - 1) * limit,
take: limit,
orderBy: { [sort]: 'desc' }
});
const total = await db.users.count({ where: query });
res.json({
data: users,
meta: { page, limit, total },
links: {
self: `/users?page=${page}`,
next: page * limit < total ? `/users?page=${page + 1}` : null
}
});
});
GET /users?sort=name - by name ascending
/users?sort=-createdAt - by createdAt descending
/users?sort=role,-createdAt - sort fields
/users?fields=id,name,email - specified fields
5. Versioning
GET /api/v1/users
GET /api/v2/users
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);
GET /api/users
Headers: { "Accept-Version": "v2" }
app.use('/api/v1', (req, res, next) => {
res.set('X-API-Deprecation', 'v1 will be deprecated on 2024-12-31');
res.set('X-API-Upgrade', 'See /api/v2 for latest version');
next();
});
6. Authentication & Authorization
GET /api/users
Headers: { "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
function authenticate(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use('/api/', limiter);
/api/users
: { : }
() {
apiKey = req.();
(!(apiKey)) {
res.().({ : });
}
();
}
7. HATEOAS (Hypermedia)
{
"data": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
},
"links": {
"self": "/users/123",
"posts": "/users/123/posts",
"followers": "/users/123/followers"
}
}
{
"data": {
"id": "456",
"status": "pending",
"amount": 100
},
"actions": {
"approve": {
"method": "POST",
"href": "/orders/456/approve"
},
"cancel": {
"method": "DELETE",
"href": "/orders/456"
}
}
}
GraphQL API Design
1. Schema Definition
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
published: Boolean!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!
post ID Post
posts ID, Boolean Post
createUser CreateUserInput User
updateUser ID, UpdateUserInput User
deleteUser ID Boolean
createPost CreatePostInput Post
publishPost ID Post
CreateUserInput
String
String
UpdateUserInput
String
String
CreatePostInput
String
String
ID
2. Resolvers
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findMany({
where: { id: { in: ids } }
});
return ids.map(id => users.find(u => u.id === id));
});
const resolvers = {
Query: {
user: async (_, { id }) => {
return await userLoader.load(id);
},
users: async (_, { limit = 20, offset = 0 }) => {
return await db.users.findMany({
take: limit,
skip: offset
});
}
},
Mutation: {
createUser: async (_, { input }) => {
return await db.users.create(input);
},
updateUser: (_, { id, input }) => {
db..(id, input);
}
},
: {
: (user) => {
db..({
: { : user. }
});
}
}
};
3. Error Handling
import { GraphQLError } from 'graphql';
const resolvers = {
Query: {
user: async (_, { id }) => {
const user = await db.users.findById(id);
if (!user) {
throw new GraphQLError('User not found', {
extensions: {
code: 'USER_NOT_FOUND',
id
}
});
}
return user;
}
},
Mutation: {
createUser: async (_, { input }) => {
try {
return await db.users.create(input);
} catch (error) {
if (error.code === 'P2002') {
throw new GraphQLError('Email already exists', {
extensions: {
code: 'DUPLICATE_EMAIL',
field: 'email'
}
});
}
throw error;
}
}
}
};
API Documentation
1. OpenAPI/Swagger (REST)
app.get('/users', async (req, res) => {
});
2. GraphQL Documentation
"""
Represents a user in the system
"""
type User {
"""Unique identifier"""
id: ID!
"""User's full name"""
name: String!
"""User's email address"""
email: String!
"""Posts authored by this user"""
posts: [Post!]!
}
"""
Create a new user
"""
createUser(
"""User details"""
input: CreateUserInput!
): User!
API Best Practices
1. Idempotency
app.put('/users/:id', async (req, res) => {
const user = await db.users.upsert({
where: { id: req.params.id },
create: req.body,
update: req.body
});
res.json(user);
});
app.post('/payments', async (req, res) => {
const idempotencyKey = req.get('Idempotency-Key');
if (!idempotencyKey) {
return res.status(400).json({ error: 'Idempotency-Key required' });
}
const existing = await cache.get(`payment:${idempotencyKey}`);
if (existing) {
return res.json(existing);
}
const payment = await processPayment(req.body);
await cache.set(, payment, * * );
res.().(payment);
});
2. Caching
app.get('/products/:id', async (req, res) => {
const product = await db.products.findById(req.params.id);
const etag = generateETag(product);
if (req.get('If-None-Match') === etag) {
return res.status(304).send();
}
res.set({
'ETag': etag,
'Cache-Control': 'public, max-age=300',
'Last-Modified': product.updatedAt.toUTCString()
});
res.json(product);
});
3. Webhooks
interface WebhookPayload {
event: string;
data: any;
timestamp: string;
signature: string;
}
async function sendWebhook(url: string, event: string, data: any) {
const payload = {
event,
data,
timestamp: new Date().toISOString()
};
const signature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex');
await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': signature
},
body: JSON.stringify({ ...payload, signature })
});
}
app.(, (req, res) => {
user = db..(req.);
(
,
,
user
).(.);
res.().(user);
});
API Design Checklist
REST:
□ Resource-based URLs (nouns, not verbs)
□ Proper HTTP methods and status codes
□ Consistent response format
□ Pagination for lists
□ Filtering, sorting, field selection
□ API versioning strategy
□ Authentication (Bearer tokens, API keys)
□ Rate limiting configured
□ CORS properly configured
□ Error responses standardized
GraphQL:
□ Clear, typed schema
□ Efficient resolvers (DataLoader)
□ Pagination implemented (cursor or offset)
□ Error handling with codes
□ Authentication & authorization
□ Query complexity limits
□ Depth limiting
□ Introspection disabled in production
Documentation:
□ OpenAPI/GraphQL schema published
□ Example requests/responses
□ Authentication docs
□ Error codes documented
□ Changelog maintained
□ Migration guides for breaking changes
Performance:
□ Database queries optimized
□ N+1 queries eliminated
□ Response caching
□ Compression enabled (gzip)
□ CDN for static responses
Security:
□ Input validation on all endpoints
□ SQL injection prevention
□ Rate limiting per endpoint
□ API key rotation supported
□ Audit logging for sensitive operations
Resources
Remember: Great APIs are predictable, well-documented, and easy to use. Design for your API consumers, not just your implementation.