Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.
Add this skill
npx mdskills install sickn33/cc-skill-backend-patternsComprehensive backend patterns with well-structured examples but lacks agent trigger conditions
1---2name: backend-patterns3description: Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.4author: affaan-m5version: "1.0"6---78# Backend Development Patterns910Backend architecture patterns and best practices for scalable server-side applications.1112## API Design Patterns1314### RESTful API Structure1516```typescript17// ✅ Resource-based URLs18GET /api/markets # List resources19GET /api/markets/:id # Get single resource20POST /api/markets # Create resource21PUT /api/markets/:id # Replace resource22PATCH /api/markets/:id # Update resource23DELETE /api/markets/:id # Delete resource2425// ✅ Query parameters for filtering, sorting, pagination26GET /api/markets?status=active&sort=volume&limit=20&offset=027```2829### Repository Pattern3031```typescript32// Abstract data access logic33interface MarketRepository {34 findAll(filters?: MarketFilters): Promise<Market[]>35 findById(id: string): Promise<Market | null>36 create(data: CreateMarketDto): Promise<Market>37 update(id: string, data: UpdateMarketDto): Promise<Market>38 delete(id: string): Promise<void>39}4041class SupabaseMarketRepository implements MarketRepository {42 async findAll(filters?: MarketFilters): Promise<Market[]> {43 let query = supabase.from('markets').select('*')4445 if (filters?.status) {46 query = query.eq('status', filters.status)47 }4849 if (filters?.limit) {50 query = query.limit(filters.limit)51 }5253 const { data, error } = await query5455 if (error) throw new Error(error.message)56 return data57 }5859 // Other methods...60}61```6263### Service Layer Pattern6465```typescript66// Business logic separated from data access67class MarketService {68 constructor(private marketRepo: MarketRepository) {}6970 async searchMarkets(query: string, limit: number = 10): Promise<Market[]> {71 // Business logic72 const embedding = await generateEmbedding(query)73 const results = await this.vectorSearch(embedding, limit)7475 // Fetch full data76 const markets = await this.marketRepo.findByIds(results.map(r => r.id))7778 // Sort by similarity79 return markets.sort((a, b) => {80 const scoreA = results.find(r => r.id === a.id)?.score || 081 const scoreB = results.find(r => r.id === b.id)?.score || 082 return scoreA - scoreB83 })84 }8586 private async vectorSearch(embedding: number[], limit: number) {87 // Vector search implementation88 }89}90```9192### Middleware Pattern9394```typescript95// Request/response processing pipeline96export function withAuth(handler: NextApiHandler): NextApiHandler {97 return async (req, res) => {98 const token = req.headers.authorization?.replace('Bearer ', '')99100 if (!token) {101 return res.status(401).json({ error: 'Unauthorized' })102 }103104 try {105 const user = await verifyToken(token)106 req.user = user107 return handler(req, res)108 } catch (error) {109 return res.status(401).json({ error: 'Invalid token' })110 }111 }112}113114// Usage115export default withAuth(async (req, res) => {116 // Handler has access to req.user117})118```119120## Database Patterns121122### Query Optimization123124```typescript125// ✅ GOOD: Select only needed columns126const { data } = await supabase127 .from('markets')128 .select('id, name, status, volume')129 .eq('status', 'active')130 .order('volume', { ascending: false })131 .limit(10)132133// ❌ BAD: Select everything134const { data } = await supabase135 .from('markets')136 .select('*')137```138139### N+1 Query Prevention140141```typescript142// ❌ BAD: N+1 query problem143const markets = await getMarkets()144for (const market of markets) {145 market.creator = await getUser(market.creator_id) // N queries146}147148// ✅ GOOD: Batch fetch149const markets = await getMarkets()150const creatorIds = markets.map(m => m.creator_id)151const creators = await getUsers(creatorIds) // 1 query152const creatorMap = new Map(creators.map(c => [c.id, c]))153154markets.forEach(market => {155 market.creator = creatorMap.get(market.creator_id)156})157```158159### Transaction Pattern160161```typescript162async function createMarketWithPosition(163 marketData: CreateMarketDto,164 positionData: CreatePositionDto165) {166 // Use Supabase transaction167 const { data, error } = await supabase.rpc('create_market_with_position', {168 market_data: marketData,169 position_data: positionData170 })171172 if (error) throw new Error('Transaction failed')173 return data174}175176// SQL function in Supabase177CREATE OR REPLACE FUNCTION create_market_with_position(178 market_data jsonb,179 position_data jsonb180)181RETURNS jsonb182LANGUAGE plpgsql183AS $$184BEGIN185 -- Start transaction automatically186 INSERT INTO markets VALUES (market_data);187 INSERT INTO positions VALUES (position_data);188 RETURN jsonb_build_object('success', true);189EXCEPTION190 WHEN OTHERS THEN191 -- Rollback happens automatically192 RETURN jsonb_build_object('success', false, 'error', SQLERRM);193END;194$$;195```196197## Caching Strategies198199### Redis Caching Layer200201```typescript202class CachedMarketRepository implements MarketRepository {203 constructor(204 private baseRepo: MarketRepository,205 private redis: RedisClient206 ) {}207208 async findById(id: string): Promise<Market | null> {209 // Check cache first210 const cached = await this.redis.get(`market:${id}`)211212 if (cached) {213 return JSON.parse(cached)214 }215216 // Cache miss - fetch from database217 const market = await this.baseRepo.findById(id)218219 if (market) {220 // Cache for 5 minutes221 await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))222 }223224 return market225 }226227 async invalidateCache(id: string): Promise<void> {228 await this.redis.del(`market:${id}`)229 }230}231```232233### Cache-Aside Pattern234235```typescript236async function getMarketWithCache(id: string): Promise<Market> {237 const cacheKey = `market:${id}`238239 // Try cache240 const cached = await redis.get(cacheKey)241 if (cached) return JSON.parse(cached)242243 // Cache miss - fetch from DB244 const market = await db.markets.findUnique({ where: { id } })245246 if (!market) throw new Error('Market not found')247248 // Update cache249 await redis.setex(cacheKey, 300, JSON.stringify(market))250251 return market252}253```254255## Error Handling Patterns256257### Centralized Error Handler258259```typescript260class ApiError extends Error {261 constructor(262 public statusCode: number,263 public message: string,264 public isOperational = true265 ) {266 super(message)267 Object.setPrototypeOf(this, ApiError.prototype)268 }269}270271export function errorHandler(error: unknown, req: Request): Response {272 if (error instanceof ApiError) {273 return NextResponse.json({274 success: false,275 error: error.message276 }, { status: error.statusCode })277 }278279 if (error instanceof z.ZodError) {280 return NextResponse.json({281 success: false,282 error: 'Validation failed',283 details: error.errors284 }, { status: 400 })285 }286287 // Log unexpected errors288 console.error('Unexpected error:', error)289290 return NextResponse.json({291 success: false,292 error: 'Internal server error'293 }, { status: 500 })294}295296// Usage297export async function GET(request: Request) {298 try {299 const data = await fetchData()300 return NextResponse.json({ success: true, data })301 } catch (error) {302 return errorHandler(error, request)303 }304}305```306307### Retry with Exponential Backoff308309```typescript310async function fetchWithRetry<T>(311 fn: () => Promise<T>,312 maxRetries = 3313): Promise<T> {314 let lastError: Error315316 for (let i = 0; i < maxRetries; i++) {317 try {318 return await fn()319 } catch (error) {320 lastError = error as Error321322 if (i < maxRetries - 1) {323 // Exponential backoff: 1s, 2s, 4s324 const delay = Math.pow(2, i) * 1000325 await new Promise(resolve => setTimeout(resolve, delay))326 }327 }328 }329330 throw lastError!331}332333// Usage334const data = await fetchWithRetry(() => fetchFromAPI())335```336337## Authentication & Authorization338339### JWT Token Validation340341```typescript342import jwt from 'jsonwebtoken'343344interface JWTPayload {345 userId: string346 email: string347 role: 'admin' | 'user'348}349350export function verifyToken(token: string): JWTPayload {351 try {352 const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload353 return payload354 } catch (error) {355 throw new ApiError(401, 'Invalid token')356 }357}358359export async function requireAuth(request: Request) {360 const token = request.headers.get('authorization')?.replace('Bearer ', '')361362 if (!token) {363 throw new ApiError(401, 'Missing authorization token')364 }365366 return verifyToken(token)367}368369// Usage in API route370export async function GET(request: Request) {371 const user = await requireAuth(request)372373 const data = await getDataForUser(user.userId)374375 return NextResponse.json({ success: true, data })376}377```378379### Role-Based Access Control380381```typescript382type Permission = 'read' | 'write' | 'delete' | 'admin'383384interface User {385 id: string386 role: 'admin' | 'moderator' | 'user'387}388389const rolePermissions: Record<User['role'], Permission[]> = {390 admin: ['read', 'write', 'delete', 'admin'],391 moderator: ['read', 'write', 'delete'],392 user: ['read', 'write']393}394395export function hasPermission(user: User, permission: Permission): boolean {396 return rolePermissions[user.role].includes(permission)397}398399export function requirePermission(permission: Permission) {400 return async (request: Request) => {401 const user = await requireAuth(request)402403 if (!hasPermission(user, permission)) {404 throw new ApiError(403, 'Insufficient permissions')405 }406407 return user408 }409}410411// Usage412export const DELETE = requirePermission('delete')(async (request: Request) => {413 // Handler with permission check414})415```416417## Rate Limiting418419### Simple In-Memory Rate Limiter420421```typescript422class RateLimiter {423 private requests = new Map<string, number[]>()424425 async checkLimit(426 identifier: string,427 maxRequests: number,428 windowMs: number429 ): Promise<boolean> {430 const now = Date.now()431 const requests = this.requests.get(identifier) || []432433 // Remove old requests outside window434 const recentRequests = requests.filter(time => now - time < windowMs)435436 if (recentRequests.length >= maxRequests) {437 return false // Rate limit exceeded438 }439440 // Add current request441 recentRequests.push(now)442 this.requests.set(identifier, recentRequests)443444 return true445 }446}447448const limiter = new RateLimiter()449450export async function GET(request: Request) {451 const ip = request.headers.get('x-forwarded-for') || 'unknown'452453 const allowed = await limiter.checkLimit(ip, 100, 60000) // 100 req/min454455 if (!allowed) {456 return NextResponse.json({457 error: 'Rate limit exceeded'458 }, { status: 429 })459 }460461 // Continue with request462}463```464465## Background Jobs & Queues466467### Simple Queue Pattern468469```typescript470class JobQueue<T> {471 private queue: T[] = []472 private processing = false473474 async add(job: T): Promise<void> {475 this.queue.push(job)476477 if (!this.processing) {478 this.process()479 }480 }481482 private async process(): Promise<void> {483 this.processing = true484485 while (this.queue.length > 0) {486 const job = this.queue.shift()!487488 try {489 await this.execute(job)490 } catch (error) {491 console.error('Job failed:', error)492 }493 }494495 this.processing = false496 }497498 private async execute(job: T): Promise<void> {499 // Job execution logic500 }501}502503// Usage for indexing markets504interface IndexJob {505 marketId: string506}507508const indexQueue = new JobQueue<IndexJob>()509510export async function POST(request: Request) {511 const { marketId } = await request.json()512513 // Add to queue instead of blocking514 await indexQueue.add({ marketId })515516 return NextResponse.json({ success: true, message: 'Job queued' })517}518```519520## Logging & Monitoring521522### Structured Logging523524```typescript525interface LogContext {526 userId?: string527 requestId?: string528 method?: string529 path?: string530 [key: string]: unknown531}532533class Logger {534 log(level: 'info' | 'warn' | 'error', message: string, context?: LogContext) {535 const entry = {536 timestamp: new Date().toISOString(),537 level,538 message,539 ...context540 }541542 console.log(JSON.stringify(entry))543 }544545 info(message: string, context?: LogContext) {546 this.log('info', message, context)547 }548549 warn(message: string, context?: LogContext) {550 this.log('warn', message, context)551 }552553 error(message: string, error: Error, context?: LogContext) {554 this.log('error', message, {555 ...context,556 error: error.message,557 stack: error.stack558 })559 }560}561562const logger = new Logger()563564// Usage565export async function GET(request: Request) {566 const requestId = crypto.randomUUID()567568 logger.info('Fetching markets', {569 requestId,570 method: 'GET',571 path: '/api/markets'572 })573574 try {575 const markets = await fetchMarkets()576 return NextResponse.json({ success: true, data: markets })577 } catch (error) {578 logger.error('Failed to fetch markets', error as Error, { requestId })579 return NextResponse.json({ error: 'Internal error' }, { status: 500 })580 }581}582```583584**Remember**: Backend patterns enable scalable, maintainable server-side applications. Choose patterns that fit your complexity level.
Full transparency — inspect the skill content before installing.