backend-patterns

Backend Development Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "backend-patterns" with this command: npx skills add oldwinter/skills/oldwinter-skills-backend-patterns

Backend Development Patterns

Backend architecture patterns and best practices for scalable server-side applications.

API Design Patterns

RESTful API Structure

// ✅ Resource-based URLs GET /api/markets # List resources GET /api/markets/:id # Get single resource POST /api/markets # Create resource PUT /api/markets/:id # Replace resource PATCH /api/markets/:id # Update resource DELETE /api/markets/:id # Delete resource

// ✅ Query parameters for filtering, sorting, pagination GET /api/markets?status=active&sort=volume&limit=20&offset=0

Repository Pattern

// Abstract data access logic interface MarketRepository { findAll(filters?: MarketFilters): Promise<Market[]> findById(id: string): Promise<Market | null> create(data: CreateMarketDto): Promise<Market> update(id: string, data: UpdateMarketDto): Promise<Market> delete(id: string): Promise<void> }

class SupabaseMarketRepository implements MarketRepository { async findAll(filters?: MarketFilters): Promise<Market[]> { let query = supabase.from('markets').select('*')

if (filters?.status) {
  query = query.eq('status', filters.status)
}

if (filters?.limit) {
  query = query.limit(filters.limit)
}

const { data, error } = await query

if (error) throw new Error(error.message)
return data

}

// Other methods... }

Service Layer Pattern

// Business logic separated from data access class MarketService { constructor(private marketRepo: MarketRepository) {}

async searchMarkets(query: string, limit: number = 10): Promise<Market[]> { // Business logic const embedding = await generateEmbedding(query) const results = await this.vectorSearch(embedding, limit)

// Fetch full data
const markets = await this.marketRepo.findByIds(results.map(r => r.id))

// Sort by similarity
return markets.sort((a, b) => {
  const scoreA = results.find(r => r.id === a.id)?.score || 0
  const scoreB = results.find(r => r.id === b.id)?.score || 0
  return scoreA - scoreB
})

}

private async vectorSearch(embedding: number[], limit: number) { // Vector search implementation } }

Middleware Pattern

// Request/response processing pipeline export function withAuth(handler: NextApiHandler): NextApiHandler { return async (req, res) => { const token = req.headers.authorization?.replace('Bearer ', '')

if (!token) {
  return res.status(401).json({ error: 'Unauthorized' })
}

try {
  const user = await verifyToken(token)
  req.user = user
  return handler(req, res)
} catch (error) {
  return res.status(401).json({ error: 'Invalid token' })
}

} }

// Usage export default withAuth(async (req, res) => { // Handler has access to req.user })

Database Patterns

Query Optimization

// ✅ GOOD: Select only needed columns const { data } = await supabase .from('markets') .select('id, name, status, volume') .eq('status', 'active') .order('volume', { ascending: false }) .limit(10)

// ❌ BAD: Select everything const { data } = await supabase .from('markets') .select('*')

N+1 Query Prevention

// ❌ BAD: N+1 query problem const markets = await getMarkets() for (const market of markets) { market.creator = await getUser(market.creator_id) // N queries }

// ✅ GOOD: Batch fetch const markets = await getMarkets() const creatorIds = markets.map(m => m.creator_id) const creators = await getUsers(creatorIds) // 1 query const creatorMap = new Map(creators.map(c => [c.id, c]))

markets.forEach(market => { market.creator = creatorMap.get(market.creator_id) })

Transaction Pattern

async function createMarketWithPosition( marketData: CreateMarketDto, positionData: CreatePositionDto ) { // Use Supabase transaction const { data, error } = await supabase.rpc('create_market_with_position', { market_data: marketData, position_data: positionData })

if (error) throw new Error('Transaction failed') return data }

// SQL function in Supabase CREATE OR REPLACE FUNCTION create_market_with_position( market_data jsonb, position_data jsonb ) RETURNS jsonb LANGUAGE plpgsql AS $$ BEGIN -- Start transaction automatically INSERT INTO markets VALUES (market_data); INSERT INTO positions VALUES (position_data); RETURN jsonb_build_object('success', true); EXCEPTION WHEN OTHERS THEN -- Rollback happens automatically RETURN jsonb_build_object('success', false, 'error', SQLERRM); END; $$;

Caching Strategies

Redis Caching Layer

class CachedMarketRepository implements MarketRepository { constructor( private baseRepo: MarketRepository, private redis: RedisClient ) {}

async findById(id: string): Promise<Market | null> { // Check cache first const cached = await this.redis.get(market:${id})

if (cached) {
  return JSON.parse(cached)
}

// Cache miss - fetch from database
const market = await this.baseRepo.findById(id)

if (market) {
  // Cache for 5 minutes
  await this.redis.setex(`market:${id}`, 300, JSON.stringify(market))
}

return market

}

async invalidateCache(id: string): Promise<void> { await this.redis.del(market:${id}) } }

Cache-Aside Pattern

async function getMarketWithCache(id: string): Promise<Market> { const cacheKey = market:${id}

// Try cache const cached = await redis.get(cacheKey) if (cached) return JSON.parse(cached)

// Cache miss - fetch from DB const market = await db.markets.findUnique({ where: { id } })

if (!market) throw new Error('Market not found')

// Update cache await redis.setex(cacheKey, 300, JSON.stringify(market))

return market }

Error Handling Patterns

Centralized Error Handler

class ApiError extends Error { constructor( public statusCode: number, public message: string, public isOperational = true ) { super(message) Object.setPrototypeOf(this, ApiError.prototype) } }

export function errorHandler(error: unknown, req: Request): Response { if (error instanceof ApiError) { return NextResponse.json({ success: false, error: error.message }, { status: error.statusCode }) }

if (error instanceof z.ZodError) { return NextResponse.json({ success: false, error: 'Validation failed', details: error.errors }, { status: 400 }) }

// Log unexpected errors console.error('Unexpected error:', error)

return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 }) }

// Usage export async function GET(request: Request) { try { const data = await fetchData() return NextResponse.json({ success: true, data }) } catch (error) { return errorHandler(error, request) } }

Retry with Exponential Backoff

async function fetchWithRetry<T>( fn: () => Promise<T>, maxRetries = 3 ): Promise<T> { let lastError: Error

for (let i = 0; i < maxRetries; i++) { try { return await fn() } catch (error) { lastError = error as Error

  if (i &#x3C; maxRetries - 1) {
    // Exponential backoff: 1s, 2s, 4s
    const delay = Math.pow(2, i) * 1000
    await new Promise(resolve => setTimeout(resolve, delay))
  }
}

}

throw lastError! }

// Usage const data = await fetchWithRetry(() => fetchFromAPI())

Authentication & Authorization

JWT Token Validation

import jwt from 'jsonwebtoken'

interface JWTPayload { userId: string email: string role: 'admin' | 'user' }

export function verifyToken(token: string): JWTPayload { try { const payload = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload return payload } catch (error) { throw new ApiError(401, 'Invalid token') } }

export async function requireAuth(request: Request) { const token = request.headers.get('authorization')?.replace('Bearer ', '')

if (!token) { throw new ApiError(401, 'Missing authorization token') }

return verifyToken(token) }

// Usage in API route export async function GET(request: Request) { const user = await requireAuth(request)

const data = await getDataForUser(user.userId)

return NextResponse.json({ success: true, data }) }

Role-Based Access Control

type Permission = 'read' | 'write' | 'delete' | 'admin'

interface User { id: string role: 'admin' | 'moderator' | 'user' }

const rolePermissions: Record<User['role'], Permission[]> = { admin: ['read', 'write', 'delete', 'admin'], moderator: ['read', 'write', 'delete'], user: ['read', 'write'] }

export function hasPermission(user: User, permission: Permission): boolean { return rolePermissions[user.role].includes(permission) }

export function requirePermission(permission: Permission) { return (handler: (request: Request, user: User) => Promise<Response>) => { return async (request: Request) => { const user = await requireAuth(request)

  if (!hasPermission(user, permission)) {
    throw new ApiError(403, 'Insufficient permissions')
  }

  return handler(request, user)
}

} }

// Usage - HOF wraps the handler export const DELETE = requirePermission('delete')( async (request: Request, user: User) => { // Handler receives authenticated user with verified permission return new Response('Deleted', { status: 200 }) } )

Rate Limiting

Simple In-Memory Rate Limiter

class RateLimiter { private requests = new Map<string, number[]>()

async checkLimit( identifier: string, maxRequests: number, windowMs: number ): Promise<boolean> { const now = Date.now() const requests = this.requests.get(identifier) || []

// Remove old requests outside window
const recentRequests = requests.filter(time => now - time &#x3C; windowMs)

if (recentRequests.length >= maxRequests) {
  return false  // Rate limit exceeded
}

// Add current request
recentRequests.push(now)
this.requests.set(identifier, recentRequests)

return true

} }

const limiter = new RateLimiter()

export async function GET(request: Request) { const ip = request.headers.get('x-forwarded-for') || 'unknown'

const allowed = await limiter.checkLimit(ip, 100, 60000) // 100 req/min

if (!allowed) { return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 }) }

// Continue with request }

Background Jobs & Queues

Simple Queue Pattern

class JobQueue<T> { private queue: T[] = [] private processing = false

async add(job: T): Promise<void> { this.queue.push(job)

if (!this.processing) {
  this.process()
}

}

private async process(): Promise<void> { this.processing = true

while (this.queue.length > 0) {
  const job = this.queue.shift()!

  try {
    await this.execute(job)
  } catch (error) {
    console.error('Job failed:', error)
  }
}

this.processing = false

}

private async execute(job: T): Promise<void> { // Job execution logic } }

// Usage for indexing markets interface IndexJob { marketId: string }

const indexQueue = new JobQueue<IndexJob>()

export async function POST(request: Request) { const { marketId } = await request.json()

// Add to queue instead of blocking await indexQueue.add({ marketId })

return NextResponse.json({ success: true, message: 'Job queued' }) }

Logging & Monitoring

Structured Logging

interface LogContext { userId?: string requestId?: string method?: string path?: string [key: string]: unknown }

class Logger { log(level: 'info' | 'warn' | 'error', message: string, context?: LogContext) { const entry = { timestamp: new Date().toISOString(), level, message, ...context }

console.log(JSON.stringify(entry))

}

info(message: string, context?: LogContext) { this.log('info', message, context) }

warn(message: string, context?: LogContext) { this.log('warn', message, context) }

error(message: string, error: Error, context?: LogContext) { this.log('error', message, { ...context, error: error.message, stack: error.stack }) } }

const logger = new Logger()

// Usage export async function GET(request: Request) { const requestId = crypto.randomUUID()

logger.info('Fetching markets', { requestId, method: 'GET', path: '/api/markets' })

try { const markets = await fetchMarkets() return NextResponse.json({ success: true, data: markets }) } catch (error) { logger.error('Failed to fetch markets', error as Error, { requestId }) return NextResponse.json({ error: 'Internal error' }, { status: 500 }) } }

Remember: Backend patterns enable scalable, maintainable server-side applications. Choose patterns that fit your complexity level.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

github-cli

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

aws-cli

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

argocd-cli

No summary provided by upstream source.

Repository SourceNeeds Review