ios-backend-patterns

Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.

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 "ios-backend-patterns" with this command: npx skills add sh-oh/ios-agent-skills/sh-oh-ios-agent-skills-ios-backend-patterns

Backend Development Patterns

Backend architecture patterns and best practices for scalable server-side applications using Node.js, Express, and Next.js API routes.

When to Apply

Use these patterns when:

  • Building REST APIs with Express or Next.js API routes
  • Designing data access layers (Repository, Service patterns)
  • Implementing authentication, authorization, or rate limiting
  • Optimizing database queries (N+1 prevention, caching)
  • Adding error handling, retries, logging, or background jobs
  • Reviewing backend code for architectural consistency

Quick Reference

PatternPurposeReference
RESTful APIResource-based URL designapi-design.md
RepositoryAbstract data access logicapi-design.md
Service LayerSeparate business logic from data accessapi-design.md
MiddlewareRequest/response processing pipelineapi-design.md
JWT AuthToken-based authenticationapi-design.md
RBACRole-based access controlapi-design.md
Rate LimitingThrottle requests per clientapi-design.md
Query OptimizationSelect only needed columnsdatabase-patterns.md
N+1 PreventionBatch fetch related datadatabase-patterns.md
TransactionsAtomic multi-table writesdatabase-patterns.md
Redis CachingCache-aside with TTLdatabase-patterns.md
Error HandlingCentralized error classesdatabase-patterns.md
Retry with BackoffResilient external callsdatabase-patterns.md
Background JobsNon-blocking async processingdatabase-patterns.md
Structured LoggingJSON logs with contextdatabase-patterns.md

Key Patterns

RESTful API Structure

// Resource-based URLs with standard HTTP methods
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 behind an interface
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
  }
}

Service Layer Pattern

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

  async searchMarkets(query: string, limit = 10): Promise<Market[]> {
    const embedding = await generateEmbedding(query)
    const results = await this.vectorSearch(embedding, limit)
    const markets = await this.marketRepo.findByIds(results.map(r => r.id))
    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
    })
  }
}

Middleware Pattern

// Request/response processing pipeline (Next.js)
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 {
      req.user = await verifyToken(token)
      return handler(req, res)
    } catch {
      return res.status(401).json({ error: 'Invalid token' })
    }
  }
}

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

Centralized Error Handling

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

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 }
    )
  }
  console.error('Unexpected error:', error)
  return NextResponse.json(
    { success: false, error: 'Internal server error' },
    { status: 500 }
  )
}

Cache-Aside Pattern

async function getMarketWithCache(id: string): Promise<Market> {
  const cached = await redis.get(`market:${id}`)
  if (cached) return JSON.parse(cached)

  const market = await db.markets.findUnique({ where: { id } })
  if (!market) throw new Error('Market not found')

  await redis.setex(`market:${id}`, 300, JSON.stringify(market))
  return market
}

Common Mistakes

1. No Data Access Abstraction

Calling the database directly from route handlers. Use the Repository pattern to keep data access testable and swappable.

2. N+1 Queries

Fetching related records in a loop instead of batch-fetching. Always collect IDs first and query once with an IN clause.

3. Missing Error Classification

Catching all errors with a generic 500 response. Use typed error classes (ApiError, ZodError) so clients receive accurate status codes.

4. No Rate Limiting on Public Endpoints

Exposing APIs without throttling invites abuse. Apply rate limiting at minimum on authentication and write endpoints.

5. Blocking the Request with Heavy Work

Running expensive operations (indexing, email, image processing) synchronously in the request handler. Use a job queue to process work in the background.


See the references/ directory for full implementations of every pattern listed above.

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.

Automation

swift-tdd-workflow

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

ios-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

foundation-models

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

swift-coding-standards

No summary provided by upstream source.

Repository SourceNeeds Review