| name | backend-development-patterns |
| description | API design, database patterns, REST/GraphQL, microservices architecture, and backend best practices |
| triggers | api, backend, REST, GraphQL, database, microservices, server, endpoint, PostgreSQL, Supabase |
| version | 1.0.0 |
| agents | senior-fullstack-developer, database-migration-specialist, solutions-architect |
| context_levels | [object Object] |
Backend Development Patterns Skill
Overview
This skill provides comprehensive backend development patterns including API design, database architecture, authentication, and scalable backend systems.
When to Use This Skill
- Designing and implementing REST/GraphQL APIs
- Database schema design and optimization
- Backend service architecture
- Microservices patterns
- Data layer implementation
Core API Design Principles (Level 1 - Always Loaded)
REST API Best Practices
URL Structure:
✅ GOOD - Resource-oriented URLs
GET /api/users # List users
GET /api/users/:id # Get specific user
POST /api/users # Create user
PUT /api/users/:id # Update user (full replace)
PATCH /api/users/:id # Update user (partial)
DELETE /api/users/:id # Delete user
# Nested resources
GET /api/users/:id/orders # Get user's orders
POST /api/users/:id/orders # Create order for user
❌ BAD - Verb-based URLs
GET /api/getUser?id=123
POST /api/createUser
POST /api/updateUser
POST /api/deleteUser
HTTP Status Codes:
// Success responses
200 OK // Successful GET, PUT, PATCH, DELETE
201 Created // Successful POST
204 No Content // Successful DELETE (no response body)
// Client errors
400 Bad Request // Invalid input data
401 Unauthorized // Missing or invalid authentication
403 Forbidden // Valid auth but insufficient permissions
404 Not Found // Resource doesn't exist
409 Conflict // Resource conflict (e.g., duplicate email)
422 Unprocessable // Validation errors
// Server errors
500 Internal Error // Unexpected server error
503 Service Unavail // Temporary unavailability
// Example usage
app.post('/api/users', async (req, res) => {
try {
const user = await userService.create(req.body);
res.status(201).json(user); // 201 Created
} catch (error) {
if (error instanceof ValidationError) {
res.status(422).json({ error: error.message }); // 422 Validation
} else if (error instanceof ConflictError) {
res.status(409).json({ error: error.message }); // 409 Conflict
} else {
res.status(500).json({ error: 'Internal server error' }); // 500
}
}
});
Request/Response Format:
// ✅ GOOD - Consistent response structure
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
code: string;
message: string;
details?: any;
};
meta?: {
timestamp: string;
requestId: string;
};
}
// Success response
{
"success": true,
"data": {
"id": "123",
"email": "user@example.com",
"name": "John Doe"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "req_abc123"
}
}
// Error response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": {
"field": "email",
"value": "invalid-email"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"requestId": "req_abc123"
}
}
Database Design Patterns
Schema Design Best Practices:
-- ✅ GOOD - Proper constraints and indexes
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Index frequently queried fields
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
-- Foreign key relationships
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
total DECIMAL(10, 2) NOT NULL CHECK (total >= 0),
status VARCHAR(50) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
-- ❌ BAD - No constraints, poor naming
CREATE TABLE user_table (
id INT,
mail VARCHAR(1000),
data TEXT
);
Query Optimization:
// ✅ GOOD - Efficient query with specific fields
async function getUserOrders(userId: string) {
return db.query(`
SELECT
o.id,
o.total,
o.status,
o.created_at
FROM orders o
WHERE o.user_id = $1
ORDER BY o.created_at DESC
LIMIT 20
`, [userId]);
}
// ❌ BAD - SELECT * and no limit
async function getUserOrders(userId: string) {
return db.query(`
SELECT * FROM orders WHERE user_id = $1
`, [userId]);
}
// ✅ GOOD - Avoid N+1 queries with JOIN
async function getUsersWithOrders() {
const result = await db.query(`
SELECT
u.id as user_id,
u.name,
u.email,
json_agg(
json_build_object(
'id', o.id,
'total', o.total,
'status', o.status
)
) as orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email
`);
return result.rows;
}
Repository Pattern
// Generic repository interface
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(options?: FindOptions): Promise<T[]>;
create(data: Partial<T>): Promise<T>;
update(id: string, data: Partial<T>): Promise<T>;
delete(id: string): Promise<boolean>;
}
// Implementation for User entity
class UserRepository implements Repository<User> {
constructor(private db: Database) {}
async findById(id: string): Promise<User | null> {
const result = await this.db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result.rows[0] ? this.mapToUser(result.rows[0]) : null;
}
async findByEmail(email: string): Promise<User | null> {
const result = await this.db.query(
'SELECT * FROM users WHERE email = $1',
[email]
);
return result.rows[0] ? this.mapToUser(result.rows[0]) : null;
}
async create(data: CreateUserDto): Promise<User> {
const result = await this.db.query(
`INSERT INTO users (email, name, password_hash)
VALUES ($1, $2, $3)
RETURNING *`,
[data.email, data.name, data.passwordHash]
);
return this.mapToUser(result.rows[0]);
}
async update(id: string, data: Partial<User>): Promise<User> {
const updates: string[] = [];
const values: any[] = [];
let paramCount = 1;
Object.entries(data).forEach(([key, value]) => {
updates.push(`${key} = $${paramCount}`);
values.push(value);
paramCount++;
});
values.push(id);
const result = await this.db.query(
`UPDATE users
SET ${updates.join(', ')}, updated_at = NOW()
WHERE id = $${paramCount}
RETURNING *`,
values
);
return this.mapToUser(result.rows[0]);
}
private mapToUser(row: any): User {
return {
id: row.id,
email: row.email,
name: row.name,
createdAt: row.created_at,
updatedAt: row.updated_at,
};
}
}
Service Layer Pattern
// Business logic layer
class UserService {
constructor(
private userRepo: UserRepository,
private emailService: EmailService,
private cacheService: CacheService,
private logger: Logger
) {}
async createUser(dto: CreateUserDto): Promise<User> {
// 1. Validate input
this.validateUserDto(dto);
// 2. Check for duplicates
const existing = await this.userRepo.findByEmail(dto.email);
if (existing) {
throw new ConflictError('Email already registered');
}
// 3. Hash password
const passwordHash = await bcrypt.hash(dto.password, 12);
// 4. Create user
const user = await this.userRepo.create({
email: dto.email,
name: dto.name,
passwordHash,
});
// 5. Send welcome email (async, don't block)
this.emailService.sendWelcomeEmail(user.email)
.catch(err => this.logger.error('Failed to send welcome email', err));
// 6. Invalidate cache
await this.cacheService.delete(`users:all`);
// 7. Log event
this.logger.info('User created', { userId: user.id });
return user;
}
async getUserById(id: string): Promise<User> {
// Try cache first
const cacheKey = `user:${id}`;
const cached = await this.cacheService.get<User>(cacheKey);
if (cached) {
return cached;
}
// Fetch from database
const user = await this.userRepo.findById(id);
if (!user) {
throw new NotFoundError('User not found');
}
// Cache for 5 minutes
await this.cacheService.set(cacheKey, user, 300);
return user;
}
private validateUserDto(dto: CreateUserDto): void {
if (!validator.isEmail(dto.email)) {
throw new ValidationError('Invalid email format');
}
if (dto.password.length < 8) {
throw new ValidationError('Password must be at least 8 characters');
}
if (dto.name.trim().length < 2) {
throw new ValidationError('Name must be at least 2 characters');
}
}
}
Authentication & Authorization
JWT Authentication
import jwt from 'jsonwebtoken';
interface JwtPayload {
userId: string;
email: string;
role: string;
}
class AuthService {
private readonly JWT_SECRET = process.env.JWT_SECRET!;
private readonly JWT_EXPIRES_IN = '1h';
private readonly REFRESH_TOKEN_EXPIRES_IN = '7d';
generateAccessToken(user: User): string {
const payload: JwtPayload = {
userId: user.id,
email: user.email,
role: user.role,
};
return jwt.sign(payload, this.JWT_SECRET, {
expiresIn: this.JWT_EXPIRES_IN,
algorithm: 'HS256',
});
}
generateRefreshToken(user: User): string {
return jwt.sign(
{ userId: user.id },
this.JWT_SECRET,
{ expiresIn: this.REFRESH_TOKEN_EXPIRES_IN }
);
}
verifyToken(token: string): JwtPayload {
try {
return jwt.verify(token, this.JWT_SECRET) as JwtPayload;
} catch (error) {
throw new UnauthorizedError('Invalid or expired token');
}
}
}
// Middleware
function authenticateJWT(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing authentication token' });
}
const token = authHeader.substring(7);
try {
const payload = authService.verifyToken(token);
req.user = payload;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
// Authorization middleware
function requireRole(...roles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage
app.get('/api/admin/users', authenticateJWT, requireRole('admin'), async (req, res) => {
// Only authenticated admins can access
const users = await userService.getAllUsers();
res.json(users);
});
API Versioning
// URL-based versioning
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
// v1 route (old)
v1Router.get('/users/:id', async (req, res) => {
const user = await userService.getUser(req.params.id);
res.json(user); // Old response format
});
// v2 route (new - includes additional fields)
v2Router.get('/users/:id', async (req, res) => {
const user = await userService.getUser(req.params.id);
const enriched = await userService.enrichUserData(user);
res.json(enriched); // New response format with more data
});
Pagination & Filtering
// Query parameters for pagination
interface PaginationParams {
page: number;
limit: number;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
filters?: Record<string, any>;
}
async function getUsers(params: PaginationParams) {
const { page = 1, limit = 20, sortBy = 'created_at', sortOrder = 'desc' } = params;
const offset = (page - 1) * limit;
const result = await db.query(`
SELECT * FROM users
WHERE email LIKE $1
ORDER BY ${sortBy} ${sortOrder}
LIMIT $2 OFFSET $3
`, [`%${params.filters?.email || ''}%`, limit, offset]);
const countResult = await db.query('SELECT COUNT(*) FROM users');
const total = parseInt(countResult.rows[0].count);
return {
data: result.rows,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1,
},
};
}
// API endpoint
app.get('/api/users', async (req, res) => {
const result = await getUsers({
page: parseInt(req.query.page as string) || 1,
limit: parseInt(req.query.limit as string) || 20,
sortBy: req.query.sortBy as string,
sortOrder: req.query.sortOrder as 'asc' | 'desc',
filters: req.query.filters as any,
});
res.json(result);
});
Rate Limiting
import rateLimit from 'express-rate-limit';
// Global rate limit
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Too many requests, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', limiter);
// Strict limit for authentication endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts per 15 minutes
skipSuccessfulRequests: true,
});
app.post('/api/auth/login', authLimiter, loginHandler);
Detailed Patterns (Level 2 - Load on Request)
See companion files:
graphql-patterns.md- GraphQL schema design and resolversmicroservices-patterns.md- Service communication and orchestrationcaching-strategies.md- Redis patterns and cache invalidation
Code Examples (Level 3 - Load When Needed)
See examples directory:
examples/rest-api-complete.ts- Full REST API implementationexamples/graphql-server.ts- Complete GraphQL setupexamples/auth-flow-complete.ts- Authentication implementation
Integration with Agents
senior-fullstack-developer:
- Primary agent for backend implementation
- Uses these patterns for all API development
- References for database design
database-migration-specialist:
- Uses database patterns for schema design
- References optimization patterns
solutions-architect:
- Uses these patterns for system design
- Evaluates architecture decisions
Version 1.0.0 | REST & GraphQL Ready | Scalable Patterns