Claude-skill-registry backend-api-patterns
Backend and API implementation patterns for scalability, security, and maintainability. Use when building APIs, services, and backend infrastructure.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/backend-api-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-backend-api-patterns && rm -rf "$T"
manifest:
skills/data/backend-api-patterns/SKILL.mdsource content
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
// Standard API response envelope interface ApiResponse<T> { data?: T; error?: { code: string; message: string; details?: Record<string, unknown>; }; meta?: { pagination?: { page: number; pageSize: number; total: number; totalPages: number; }; timestamp?: string; requestId?: string; }; } // Success response helper function success<T>(data: T, meta?: ApiResponse<T>['meta']): ApiResponse<T> { return { data, meta }; } // Error response helper function error( code: string, message: string, details?: Record<string, unknown> ): ApiResponse<never> { return { error: { code, message, details } }; } // Paginated response helper function paginated<T>( data: T[], page: number, pageSize: number, total: number ): ApiResponse<T[]> { return { data, meta: { pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize), }, }, }; }
Route Handler Pattern
// Generic handler wrapper with error handling type Handler<T> = ( req: Request, context: { params: Record<string, string> } ) => Promise<T>; function createHandler<T>(handler: Handler<T>) { return async (req: Request, context: { params: Record<string, string> }) => { 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
interface Repository<T, ID = string> { findById(id: ID): Promise<T | null>; findMany(options: FindOptions<T>): Promise<T[]>; count(filter?: Partial<T>): Promise<number>; create(data: CreateInput<T>): Promise<T>; update(id: ID, data: UpdateInput<T>): Promise<T>; delete(id: ID): Promise<void>; } interface FindOptions<T> { filter?: Partial<T>; orderBy?: keyof T; orderDir?: 'asc' | 'desc'; limit?: number; offset?: number; } type CreateInput<T> = Omit<T, 'id' | 'createdAt' | 'updatedAt'>; type UpdateInput<T> = Partial<Omit<T, 'id' | 'createdAt' | 'updatedAt'>>; // Implementation class UserRepository implements Repository<User> { constructor(private db: Database) {} async findById(id: string): Promise<User | null> { return this.db.query.users.findFirst({ where: eq(users.id, id), }); } async findMany(options: FindOptions<User>): Promise<User[]> { 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
class UserService { constructor( private userRepo: Repository<User>, private cache: Cache, private eventBus: EventBus ) {} async getUser(id: string): Promise<User> { // Check cache first const cached = await this.cache.get<User>(`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<User> { // 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<User> { const user = await this.userRepo.update(id, input); // Invalidate cache await this.cache.delete(`user:${id}`); return user; } }
Authentication Patterns
JWT with Refresh Tokens
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<User>, private tokenRepo: Repository<RefreshToken>, private jwtSecret: string ) {} async login(email: string, password: string): Promise<TokenPair> { 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<TokenPair> { // 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
type Middleware = (req: Request, next: () => Promise<Response>) => Promise<Response>; // 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<string, { count: number; resetAt: number }>(); 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
// Avoid N+1 queries with eager loading async function getUsersWithOrders(): Promise<UserWithOrders[]> { // 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
async function transferFunds( fromId: string, toId: string, amount: number ): Promise<void> { 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
class CachedUserService { constructor( private userRepo: Repository<User>, private cache: Cache ) {} async getUser(id: string): Promise<User | null> { const cacheKey = `user:${id}`; // Try cache first const cached = await this.cache.get<User>(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<User> { const user = await this.userRepo.update(id, data); // Invalidate cache await this.cache.delete(`user:${id}`); return user; } }
Request Deduplication
class RequestDeduplicator { private pending = new Map<string, Promise<unknown>>(); async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> { // Return existing request if in flight const existing = this.pending.get(key); if (existing) return existing as Promise<T>; // 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<User> { 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