--- name: backend-api-patterns description: Backend and API implementation patterns for scalability, security, and maintainability. Use when building APIs, services, and backend infrastructure. --- This skill provides backend and API implementation patterns for building robust, scalable services. ## When to Invoke This Skill Automatically activate for: - API endpoint implementation - Database operations and queries - Authentication and authorization - Caching and performance optimization - Service architecture design ## API Design Patterns ### Consistent Response Structure ```typescript // Standard API response envelope interface ApiResponse { data?: T; error?: { code: string; message: string; details?: Record; }; meta?: { pagination?: { page: number; pageSize: number; total: number; totalPages: number; }; timestamp?: string; requestId?: string; }; } // Success response helper function success(data: T, meta?: ApiResponse['meta']): ApiResponse { return { data, meta }; } // Error response helper function error( code: string, message: string, details?: Record ): ApiResponse { return { error: { code, message, details } }; } // Paginated response helper function paginated( data: T[], page: number, pageSize: number, total: number ): ApiResponse { return { data, meta: { pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize), }, }, }; } ``` ### Route Handler Pattern ```typescript // Generic handler wrapper with error handling type Handler = ( req: Request, context: { params: Record } ) => Promise; function createHandler(handler: Handler) { return async (req: Request, context: { params: Record }) => { const requestId = crypto.randomUUID(); try { const result = await handler(req, context); return Response.json(success(result, { requestId })); } catch (err) { if (err instanceof AppError) { return Response.json( error(err.code, err.message), { status: err.statusCode } ); } console.error(`[${requestId}] Unexpected error:`, err); return Response.json( error('INTERNAL_ERROR', 'An unexpected error occurred'), { status: 500 } ); } }; } // Usage export const GET = createHandler(async (req, { params }) => { const user = await userService.findById(params.id); if (!user) throw new NotFoundError('User', params.id); return user; }); ``` ## Service Layer Pattern ### Repository Pattern ```typescript interface Repository { findById(id: ID): Promise; findMany(options: FindOptions): Promise; count(filter?: Partial): Promise; create(data: CreateInput): Promise; update(id: ID, data: UpdateInput): Promise; delete(id: ID): Promise; } interface FindOptions { filter?: Partial; orderBy?: keyof T; orderDir?: 'asc' | 'desc'; limit?: number; offset?: number; } type CreateInput = Omit; type UpdateInput = Partial>; // Implementation class UserRepository implements Repository { constructor(private db: Database) {} async findById(id: string): Promise { return this.db.query.users.findFirst({ where: eq(users.id, id), }); } async findMany(options: FindOptions): Promise { const { filter, orderBy, orderDir = 'asc', limit, offset } = options; return this.db.query.users.findMany({ where: filter ? this.buildWhere(filter) : undefined, orderBy: orderBy ? (orderDir === 'asc' ? asc : desc)(users[orderBy]) : undefined, limit, offset, }); } // ... other methods } ``` ### Service with Business Logic ```typescript class UserService { constructor( private userRepo: Repository, private cache: Cache, private eventBus: EventBus ) {} async getUser(id: string): Promise { // Check cache first const cached = await this.cache.get(`user:${id}`); if (cached) return cached; // Fetch from database const user = await this.userRepo.findById(id); if (!user) throw new NotFoundError('User', id); // Cache for future requests await this.cache.set(`user:${id}`, user, { ttl: 3600 }); return user; } async createUser(input: CreateUserInput): Promise { // Validate const existing = await this.userRepo.findMany({ filter: { email: input.email }, limit: 1, }); if (existing.length > 0) { throw new ValidationError('Email already exists', { email: 'Already in use' }); } // Hash password const hashedPassword = await hashPassword(input.password); // Create user const user = await this.userRepo.create({ ...input, password: hashedPassword, }); // Emit event for side effects await this.eventBus.emit('user.created', { userId: user.id }); return user; } async updateUser(id: string, input: UpdateUserInput): Promise { const user = await this.userRepo.update(id, input); // Invalidate cache await this.cache.delete(`user:${id}`); return user; } } ``` ## Authentication Patterns ### JWT with Refresh Tokens ```typescript interface TokenPair { accessToken: string; // Short-lived: 15 minutes refreshToken: string; // Long-lived: 7 days } interface TokenPayload { sub: string; // User ID email: string; roles: string[]; type: 'access' | 'refresh'; } class AuthService { constructor( private userRepo: Repository, private tokenRepo: Repository, private jwtSecret: string ) {} async login(email: string, password: string): Promise { const user = await this.userRepo.findMany({ filter: { email }, limit: 1, }); if (!user[0] || !await verifyPassword(password, user[0].password)) { throw new UnauthorizedError('Invalid credentials'); } return this.generateTokenPair(user[0]); } async refresh(refreshToken: string): Promise { // Verify token const payload = this.verifyToken(refreshToken); if (payload.type !== 'refresh') { throw new UnauthorizedError('Invalid token type'); } // Check if token is revoked const stored = await this.tokenRepo.findById(refreshToken); if (!stored || stored.revoked) { throw new UnauthorizedError('Token revoked'); } // Get user and generate new tokens const user = await this.userRepo.findById(payload.sub); if (!user) throw new UnauthorizedError('User not found'); // Revoke old refresh token await this.tokenRepo.update(refreshToken, { revoked: true }); return this.generateTokenPair(user); } private generateTokenPair(user: User): TokenPair { const accessToken = jwt.sign( { sub: user.id, email: user.email, roles: user.roles, type: 'access' }, this.jwtSecret, { expiresIn: '15m' } ); const refreshToken = jwt.sign( { sub: user.id, type: 'refresh' }, this.jwtSecret, { expiresIn: '7d' } ); return { accessToken, refreshToken }; } private verifyToken(token: string): TokenPayload { try { return jwt.verify(token, this.jwtSecret) as TokenPayload; } catch { throw new UnauthorizedError('Invalid or expired token'); } } } ``` ### Middleware Pattern ```typescript type Middleware = (req: Request, next: () => Promise) => Promise; // Auth middleware function authMiddleware(requiredRoles?: string[]): Middleware { return async (req, next) => { const token = req.headers.get('Authorization')?.replace('Bearer ', ''); if (!token) { return Response.json( error('UNAUTHORIZED', 'No token provided'), { status: 401 } ); } try { const payload = verifyToken(token); if (requiredRoles?.length && !requiredRoles.some(r => payload.roles.includes(r))) { return Response.json( error('FORBIDDEN', 'Insufficient permissions'), { status: 403 } ); } // Attach user to request context (req as any).user = payload; return next(); } catch { return Response.json( error('UNAUTHORIZED', 'Invalid or expired token'), { status: 401 } ); } }; } // Rate limiting middleware function rateLimitMiddleware(limit: number, windowMs: number): Middleware { const requests = new Map(); return async (req, next) => { const ip = req.headers.get('x-forwarded-for') || 'unknown'; const now = Date.now(); const record = requests.get(ip); if (!record || record.resetAt < now) { requests.set(ip, { count: 1, resetAt: now + windowMs }); return next(); } if (record.count >= limit) { return Response.json( error('RATE_LIMITED', 'Too many requests'), { status: 429 } ); } record.count++; return next(); }; } ``` ## Database Patterns ### Query Optimization ```typescript // Avoid N+1 queries with eager loading async function getUsersWithOrders(): Promise { // BAD: N+1 queries const users = await db.query.users.findMany(); for (const user of users) { user.orders = await db.query.orders.findMany({ where: eq(orders.userId, user.id), }); } // GOOD: Single query with join return db.query.users.findMany({ with: { orders: true, }, }); } // Pagination with cursor async function paginateUsers(cursor?: string, limit = 20): Promise<{ users: User[]; nextCursor: string | null; }> { const users = await db.query.users.findMany({ where: cursor ? gt(users.id, cursor) : undefined, orderBy: asc(users.id), limit: limit + 1, // Fetch one extra to check for next page }); const hasMore = users.length > limit; const data = hasMore ? users.slice(0, -1) : users; return { users: data, nextCursor: hasMore ? data[data.length - 1].id : null, }; } ``` ### Transaction Pattern ```typescript async function transferFunds( fromId: string, toId: string, amount: number ): Promise { await db.transaction(async (tx) => { // Lock rows for update const from = await tx.query.accounts.findFirst({ where: eq(accounts.id, fromId), for: 'update', }); if (!from || from.balance < amount) { throw new ValidationError('Insufficient funds', {}); } // Debit source account await tx.update(accounts) .set({ balance: from.balance - amount }) .where(eq(accounts.id, fromId)); // Credit destination account await tx.update(accounts) .set({ balance: sql`${accounts.balance} + ${amount}` }) .where(eq(accounts.id, toId)); // Log transaction await tx.insert(transactions).values({ fromId, toId, amount, type: 'transfer', }); }); } ``` ## Caching Patterns ### Cache-Aside Pattern ```typescript class CachedUserService { constructor( private userRepo: Repository, private cache: Cache ) {} async getUser(id: string): Promise { const cacheKey = `user:${id}`; // Try cache first const cached = await this.cache.get(cacheKey); if (cached) return cached; // Fetch from database const user = await this.userRepo.findById(id); // Cache the result (including null to prevent cache stampede) if (user) { await this.cache.set(cacheKey, user, { ttl: 3600 }); } else { await this.cache.set(cacheKey, null, { ttl: 60 }); // Short TTL for negative cache } return user; } async updateUser(id: string, data: UpdateUserInput): Promise { const user = await this.userRepo.update(id, data); // Invalidate cache await this.cache.delete(`user:${id}`); return user; } } ``` ### Request Deduplication ```typescript class RequestDeduplicator { private pending = new Map>(); async dedupe(key: string, fetcher: () => Promise): Promise { // Return existing request if in flight const existing = this.pending.get(key); if (existing) return existing as Promise; // Start new request const promise = fetcher().finally(() => { this.pending.delete(key); }); this.pending.set(key, promise); return promise; } } // Usage const deduplicator = new RequestDeduplicator(); async function getUser(id: string): Promise { return deduplicator.dedupe(`user:${id}`, () => userRepo.findById(id)); } ``` ## Best Practices Checklist - [ ] Use consistent API response envelope - [ ] Implement proper error hierarchy and handling - [ ] Separate concerns: routes → services → repositories - [ ] Use transactions for multi-step operations - [ ] Implement caching with proper invalidation - [ ] Avoid N+1 queries with eager loading - [ ] Use cursor-based pagination for large datasets - [ ] Implement rate limiting and request deduplication - [ ] Validate inputs at API boundaries - [ ] Log with structured data and request IDs