backend-patterns
SKILL.md
Backend Development Patterns
Backend architecture patterns and best practices for scalable Node.js/Next.js applications.
When to Use
- Designing RESTful or GraphQL APIs
- Implementing repository, service, or middleware patterns
- Optimizing database queries (N+1, indexing, transactions)
- Adding authentication, rate limiting, or caching layers
- Structuring backend code for maintainability
When NOT to Use
- Python/FastAPI backends (use fastapi-pro)
- Frontend/React patterns (use react-patterns)
- Infrastructure/DevOps (use multi-cloud-architecture)
API Design
RESTful Resource 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 // Partial update
DELETE /api/markets/:id // Delete resource
// Query params for filtering, sorting, pagination
GET /api/markets?status=active&sort=volume&limit=20&offset=0
Repository Pattern
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
class MarketService {
constructor(private repo: MarketRepository) {}
async search(query: string, limit = 10): Promise<Market[]> {
const embedding = await generateEmbedding(query)
const results = await this.vectorSearch(embedding, limit)
const markets = await this.repo.findByIds(results.map(r => r.id))
return markets.sort((a, b) =>
(results.find(r => r.id === a.id)?.score || 0) -
(results.find(r => r.id === b.id)?.score || 0)
)
}
}
Middleware Pattern
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' })
}
}
}
Database Patterns
Query Optimization
// Select only needed columns
const { data } = await supabase
.from('markets')
.select('id, name, status, volume')
.eq('status', 'active')
.order('volume', { ascending: false })
.limit(10)
N+1 Prevention
// BAD: N+1 queries
for (const market of markets) {
market.creator = await getUser(market.creator_id)
}
// GOOD: Batch fetch
const creatorIds = markets.map(m => m.creator_id)
const creators = await getUsers(creatorIds)
const map = new Map(creators.map(c => [c.id, c]))
markets.forEach(m => { m.creator = map.get(m.creator_id) })
Transactions
const { data, error } = await supabase.rpc(
'create_market_with_position',
{ market_data: marketData, position_data: positionData }
)
Caching
Cache-Aside Pattern
class CachedRepo<T> {
constructor(private base: Repository<T>, private redis: RedisClient) {}
async findById(id: string): Promise<T | null> {
const cached = await this.redis.get(`item:${id}`)
if (cached) return JSON.parse(cached)
const item = await this.base.findById(id)
if (item) await this.redis.setex(`item:${id}`, 300, JSON.stringify(item))
return item
}
async invalidate(id: string): Promise<void> {
await this.redis.del(`item:${id}`)
}
}
Error Handling
Centralized Error Handler
class ApiError extends Error {
constructor(public statusCode: number, message: string) {
super(message)
}
}
export function errorHandler(error: unknown): 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)
return NextResponse.json(
{ success: false, error: 'Internal server error' },
{ status: 500 }
)
}
Retry with Backoff
async function fetchWithRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
let lastError: Error
for (let i = 0; i < retries; i++) {
try { return await fn() }
catch (e) {
lastError = e as Error
if (i < retries - 1) await new Promise(r => setTimeout(r, 2 ** i * 1000))
}
}
throw lastError!
}
Authentication
JWT + RBAC
export function verifyToken(token: string): JWTPayload {
return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload
}
const rolePermissions: Record<string, string[]> = {
admin: ['read', 'write', 'delete', 'admin'],
moderator: ['read', 'write', 'delete'],
user: ['read', 'write'],
}
export function requirePermission(permission: string) {
return async (request: Request) => {
const user = await requireAuth(request)
if (!rolePermissions[user.role]?.includes(permission))
throw new ApiError(403, 'Insufficient permissions')
return user
}
}
Rate Limiting
class RateLimiter {
private requests = new Map<string, number[]>()
checkLimit(id: string, max: number, windowMs: number): boolean {
const now = Date.now()
const recent = (this.requests.get(id) || [])
.filter(t => now - t < windowMs)
if (recent.length >= max) return false
recent.push(now)
this.requests.set(id, recent)
return true
}
}
Structured Logging
class Logger {
log(level: string, message: string, ctx?: Record<string, unknown>) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(), level, message, ...ctx
}))
}
info(msg: string, ctx?: Record<string, unknown>) { this.log('info', msg, ctx) }
error(msg: string, err: Error, ctx?: Record<string, unknown>) {
this.log('error', msg, { ...ctx, error: err.message, stack: err.stack })
}
}
Anti-Patterns
| Don't | Do |
|---|---|
select('*') everywhere |
Select only needed columns |
| N+1 queries in loops | Batch fetch with WHERE IN |
| Business logic in routes | Service layer separation |
| Catch and swallow errors | Centralized error handler |
| Hardcode secrets | Environment variables |
| Skip input validation | Zod/Joi at API boundary |
Checklist
- RESTful resource-based URLs
- Repository pattern separates data access
- Service layer contains business logic
- N+1 queries eliminated (batch fetching)
- Caching layer for hot data
- Centralized error handling with proper status codes
- JWT authentication with RBAC
- Rate limiting on public endpoints
- Structured JSON logging
- Input validation at API boundary
Weekly Installs
1
Repository
sivag-lab/roth_mcpGitHub Stars
1
First Seen
6 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1