| name | backend-patterns |
| description | API design, database operations, error handling, and backend architecture patterns. Use when creating APIs, database schemas, or server-side logic. |
| user-invocable | false |
Backend Patterns
Comprehensive patterns for API design, database operations, error handling, and backend architecture.
When to Use
- Creating API endpoints
- Designing database schemas
- Implementing business logic
- Error handling and logging
- Background jobs and queues
- Caching strategies
API Design Patterns
RESTful API Structure
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '10')
const users = await db.users.findMany({
skip: (page - 1) * limit,
take: limit
})
return NextResponse.json({
success: true,
data: users,
pagination: {
page,
limit,
total: await db.users.count()
}
})
}
export async function POST(request: Request) {
const body = await request.json()
const validated = CreateUserSchema.parse(body)
const user = await db.users.create({ data: validated })
return NextResponse.json(
{ success: true, data: user },
{ status: 201 }
)
}
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const user = await db.users.findUnique({
where: { id: params.id }
})
if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
)
}
return NextResponse.json({ success: true, data: user })
}
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
const body = await request.json()
const validated = UpdateUserSchema.parse(body)
const user = await db.users.update({
where: { id: params.id },
data: validated
})
return NextResponse.json({ success: true, data: user })
}
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
await db.users.delete({ where: { id: params.id } })
return NextResponse.json({ success: true }, { status: 204 })
}
Consistent API Response Format
type ApiResponse<T> = {
success: true
data: T
meta?: {
pagination?: {
page: number
limit: number
total: number
}
}
} | {
success: false
error: string
details?: any
}
function successResponse<T>(data: T, meta?: any): Response {
return NextResponse.json({ success: true, data, meta })
}
function errorResponse(error: string, status: number, details?: any): Response {
return NextResponse.json(
{ success: false, error, details },
{ status }
)
}
export async function GET(request: Request) {
try {
const users = await db.users.findMany()
return successResponse(users)
} catch (error) {
return errorResponse('Failed to fetch users', 500)
}
}
API Versioning
export async function GET(request: Request) {
return NextResponse.json({ version: 1, users: [] })
}
export async function GET(request: Request) {
return NextResponse.json({ version: 2, data: { users: [] } })
}
Request Validation
import { z } from 'zod'
const CreateMarketSchema = z.object({
name: z.string().min(3).max(100),
description: z.string().min(10).max(1000),
category: z.enum(['politics', 'sports', 'entertainment', 'crypto']),
endDate: z.string().datetime(),
options: z.array(z.object({
name: z.string().min(1).max(50),
probability: z.number().min(0).max(1).optional()
})).min(2).max(10)
})
export async function POST(request: Request) {
try {
const body = await request.json()
const validated = CreateMarketSchema.parse(body)
const market = await db.markets.create({ data: validated })
return NextResponse.json({ success: true, data: market })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{
success: false,
error: 'Validation failed',
details: error.errors
},
{ status: 400 }
)
}
return NextResponse.json(
{ success: false, error: 'Internal server error' },
{ status: 500 }
)
}
}
Database Patterns
Database Schema Design
model User {
id String @id @default(cuid())
email String @unique
name String
passwordHash String
role Role @default(USER)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
comments Comment[]
@@index([email])
@@index([createdAt])
}
model Post {
id String @id @default(cuid())
title String
content String @db.Text
published Boolean @default(false)
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
comments Comment[]
tags Tag[]
@@index([authorId])
@@index([published])
@@index([createdAt])
@@fulltext([title, content])
}
model Comment {
id String @id @default(cuid())
content String @db.Text
postId String
authorId String
createdAt DateTime @default(now())
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
@@index([postId])
@@index([authorId])
}
enum Role {
USER
MODERATOR
ADMIN
}
Transaction Patterns
export async function transferFunds(fromId: string, toId: string, amount: number) {
const sender = await db.accounts.findUnique({ where: { id: fromId } })
if (sender.balance < amount) {
throw new Error('Insufficient funds')
}
await db.accounts.update({
where: { id: fromId },
data: { balance: sender.balance - amount }
})
await db.accounts.update({
where: { id: toId },
data: { balance: { increment: amount } }
})
}
export async function transferFunds(fromId: string, toId: string, amount: number) {
await db.$transaction(async (tx) => {
const sender = await tx.accounts.findUnique({
where: { id: fromId }
})
if (!sender || sender.balance < amount) {
throw new Error('Insufficient funds')
}
await tx.accounts.update({
where: { id: fromId },
data: { balance: { decrement: amount } }
})
await tx.accounts.update({
where: { id: toId },
data: { balance: { increment: amount } }
})
await tx.transactions.create({
data: {
fromId,
toId,
amount,
status: 'completed'
}
})
})
}
Query Optimization
const users = await db.users.findMany()
for (const user of users) {
user.posts = await db.posts.findMany({
where: { authorId: user.id }
})
}
const users = await db.users.findMany({
include: {
posts: true
}
})
const users = await db.users.findMany({
select: {
id: true,
name: true,
email: true,
posts: {
select: {
id: true,
title: true,
createdAt: true
}
}
}
})
const posts = await db.posts.findMany({
take: 20,
skip: (page - 1) * 20,
orderBy: { createdAt: 'desc' }
})
Supabase Patterns
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
export async function getMarkets() {
const { data, error } = await supabase
.from('markets')
.select('*')
.order('created_at', { ascending: false })
if (error) throw error
return data
}
export async function getMarketById(id: string) {
const { data, error } = await supabase
.from('markets')
.select(`
*,
creator:users(id, name, avatar),
bets(id, amount, outcome)
`)
.eq('id', id)
.single()
if (error) throw error
return data
}
export async function createMarket(market: MarketInput) {
const { data, error } = await supabase
.from('markets')
.insert(market)
.select()
.single()
if (error) throw error
return data
}
export async function searchMarkets(query: string) {
const { data, error } = await supabase
.from('markets')
.select('*')
.textSearch('fts', query, {
type: 'websearch',
config: 'english'
})
if (error) throw error
return data
}
Error Handling Patterns
Structured Error Classes
export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public code?: string,
public details?: any
) {
super(message)
this.name = this.constructor.name
}
}
export class ValidationError extends AppError {
constructor(message: string, details?: any) {
super(400, message, 'VALIDATION_ERROR', details)
}
}
export class NotFoundError extends AppError {
constructor(resource: string, id: string) {
super(404, `${resource} not found: ${id}`, 'NOT_FOUND')
}
}
export class UnauthorizedError extends AppError {
constructor(message = 'Unauthorized') {
super(401, message, 'UNAUTHORIZED')
}
}
export class ForbiddenError extends AppError {
constructor(message = 'Forbidden') {
super(403, message, 'FORBIDDEN')
}
}
export async function getUser(id: string) {
const user = await db.users.findUnique({ where: { id } })
if (!user) {
throw new NotFoundError('User', id)
}
return user
}
Global Error Handler
export function handleApiError(error: unknown): Response {
console.error('API Error:', error)
if (error instanceof AppError) {
return NextResponse.json(
{
success: false,
error: error.message,
code: error.code,
details: error.details
},
{ status: error.statusCode }
)
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{
success: false,
error: 'Validation failed',
code: 'VALIDATION_ERROR',
details: error.errors
},
{ status: 400 }
)
}
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
return NextResponse.json(
{ success: false, error: 'Duplicate entry', code: 'DUPLICATE' },
{ status: 409 }
)
}
}
return NextResponse.json(
{
success: false,
error: 'Internal server error',
code: 'INTERNAL_ERROR'
},
{ status: 500 }
)
}
export async function GET(request: Request) {
try {
const data = await fetchData()
return NextResponse.json({ success: true, data })
} catch (error) {
return handleApiError(error)
}
}
Graceful Degradation
export async function searchMarketsWithFallback(query: string) {
try {
const results = await searchVectorDatabase(query)
return { results, method: 'semantic' }
} catch (error) {
console.warn('Vector search failed, falling back to full-text:', error)
try {
const results = await supabase
.from('markets')
.select('*')
.textSearch('fts', query)
return { results: results.data, method: 'fulltext' }
} catch (fallbackError) {
console.error('Full-text search failed, using substring:', fallbackError)
const results = await supabase
.from('markets')
.select('*')
.ilike('name', `%${query}%`)
return { results: results.data, method: 'substring' }
}
}
}
Caching Patterns
Redis Caching
import Redis from 'ioredis'
const redis = new Redis(process.env.REDIS_URL!)
export async function getMarketWithCache(id: string) {
const cacheKey = `market:${id}`
const cached = await redis.get(cacheKey)
if (cached) {
return JSON.parse(cached)
}
const market = await db.markets.findUnique({ where: { id } })
if (market) {
await redis.setex(cacheKey, 300, JSON.stringify(market))
}
return market
}
export async function updateMarket(id: string, data: any) {
const market = await db.markets.update({
where: { id },
data
})
await redis.del(`market:${id}`)
return market
}
export async function invalidateMarketCaches(ids: string[]) {
const keys = ids.map(id => `market:${id}`)
if (keys.length > 0) {
await redis.del(...keys)
}
}
In-Memory Caching
import { LRUCache } from 'lru-cache'
const cache = new LRUCache<string, any>({
max: 500,
ttl: 1000 * 60 * 5,
updateAgeOnGet: true,
updateAgeOnHas: false
})
export async function getCachedData(key: string, fetcher: () => Promise<any>) {
const cached = cache.get(key)
if (cached) return cached
const data = await fetcher()
cache.set(key, data)
return data
}
Background Jobs
Queue Pattern (BullMQ)
import { Queue, Worker } from 'bullmq'
const emailQueue = new Queue('email', {
connection: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT!)
}
})
export async function sendWelcomeEmail(userId: string, email: string) {
await emailQueue.add('welcome', {
userId,
email
}, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000
}
})
}
const emailWorker = new Worker('email', async (job) => {
const { userId, email } = job.data
switch (job.name) {
case 'welcome':
await sendEmail({
to: email,
subject: 'Welcome!',
template: 'welcome',
data: { userId }
})
break
default:
throw new Error(`Unknown job: ${job.name}`)
}
}, {
connection: {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT!)
}
})
emailWorker.on('failed', (job, error) => {
console.error(`Job ${job?.id} failed:`, error)
})
Middleware Patterns
export async function withAuth(handler: Function) {
return async (request: Request, ...args: any[]) => {
const token = request.cookies.get('token')?.value
if (!token) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
const user = await verifyToken(token)
if (!user) {
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 401 }
)
}
request.user = user
return handler(request, ...args)
}
}
export const GET = withAuth(async (request: Request) => {
const userId = request.user.id
})
Best Practices
- API Design: RESTful conventions, consistent response format, versioning
- Validation: Validate all inputs with schemas (Zod, Joi)
- Error Handling: Structured error classes, global error handler
- Database: Use transactions, optimize queries, add indexes
- Caching: Cache-aside pattern, invalidation strategy
- Security: Authentication on all protected routes, authorization checks
- Logging: Structured logs, no sensitive data
- Testing: Unit tests for business logic, integration tests for APIs
- Documentation: OpenAPI/Swagger specs for APIs
- Monitoring: Health checks, error tracking (Sentry), performance metrics