11 KiB
11 KiB
Code Patterns
Common code patterns extracted from Architecture Decision Records (ADRs). Use these as templates when writing new code.
Table of Contents
- Error Handling
- Repository Patterns
- API Response Patterns
- Transaction Management
- Input Validation
- Authentication
- Caching
- Background Jobs
Error Handling
ADR: ADR-001
Repository Layer Error Handling
import { handleDbError, NotFoundError } from '../services/db/errors.db';
import { PoolClient } from 'pg';
export async function getFlyerById(id: number, client?: PoolClient): Promise<Flyer> {
const db = client || pool;
try {
const result = await db.query('SELECT * FROM flyers WHERE id = $1', [id]);
if (result.rows.length === 0) {
throw new NotFoundError('Flyer', id);
}
return result.rows[0];
} catch (error) {
throw handleDbError(error);
}
}
Route Layer Error Handling
import { sendError } from '../utils/apiResponse';
app.get('/api/flyers/:id', async (req, res) => {
try {
const flyer = await flyerDb.getFlyerById(parseInt(req.params.id));
return sendSuccess(res, flyer);
} catch (error) {
return sendError(res, error);
}
});
Custom Error Types
// NotFoundError - Entity not found
throw new NotFoundError('Flyer', id);
// ValidationError - Invalid input
throw new ValidationError('Invalid email format');
// DatabaseError - Database operation failed
throw new DatabaseError('Failed to insert flyer', originalError);
Repository Patterns
ADR: ADR-034
Method Naming Conventions
| Prefix | Returns | Not Found Behavior | Use Case |
|---|---|---|---|
get* |
Entity | Throws NotFoundError | When entity must exist |
find* |
Entity | null | Returns null | When entity may not exist |
list* |
Array | Returns [] | When returning multiple |
Get Method (Must Exist)
/**
* Get a flyer by ID. Throws NotFoundError if not found.
*/
export async function getFlyerById(id: number, client?: PoolClient): Promise<Flyer> {
const db = client || pool;
try {
const result = await db.query('SELECT * FROM flyers WHERE id = $1', [id]);
if (result.rows.length === 0) {
throw new NotFoundError('Flyer', id);
}
return result.rows[0];
} catch (error) {
throw handleDbError(error);
}
}
Find Method (May Not Exist)
/**
* Find a flyer by ID. Returns null if not found.
*/
export async function findFlyerById(id: number, client?: PoolClient): Promise<Flyer | null> {
const db = client || pool;
try {
const result = await db.query('SELECT * FROM flyers WHERE id = $1', [id]);
return result.rows[0] || null;
} catch (error) {
throw handleDbError(error);
}
}
List Method (Multiple Results)
/**
* List all active flyers. Returns empty array if none found.
*/
export async function listActiveFlyers(client?: PoolClient): Promise<Flyer[]> {
const db = client || pool;
try {
const result = await db.query(
'SELECT * FROM flyers WHERE end_date >= CURRENT_DATE ORDER BY start_date DESC',
);
return result.rows;
} catch (error) {
throw handleDbError(error);
}
}
API Response Patterns
ADR: ADR-028
Success Response
import { sendSuccess } from '../utils/apiResponse';
app.post('/api/flyers', async (req, res) => {
const flyer = await flyerService.createFlyer(req.body);
return sendSuccess(res, flyer, 'Flyer created successfully', 201);
});
Paginated Response
import { sendPaginated } from '../utils/apiResponse';
app.get('/api/flyers', async (req, res) => {
const { page = 1, pageSize = 20 } = req.query;
const { items, total } = await flyerService.listFlyers(page, pageSize);
return sendPaginated(res, {
items,
total,
page: parseInt(page),
pageSize: parseInt(pageSize),
});
});
Error Response
import { sendError } from '../utils/apiResponse';
app.get('/api/flyers/:id', async (req, res) => {
try {
const flyer = await flyerDb.getFlyerById(parseInt(req.params.id));
return sendSuccess(res, flyer);
} catch (error) {
return sendError(res, error); // Automatically maps error to correct status
}
});
Transaction Management
ADR: ADR-002
Basic Transaction
import { withTransaction } from '../services/db/transaction.db';
export async function createFlyerWithItems(
flyerData: FlyerInput,
items: FlyerItemInput[],
): Promise<Flyer> {
return withTransaction(async (client) => {
// Create flyer
const flyer = await flyerDb.createFlyer(flyerData, client);
// Create items
const createdItems = await flyerItemDb.createItems(
items.map((item) => ({ ...item, flyer_id: flyer.id })),
client,
);
// Automatically commits on success, rolls back on error
return { ...flyer, items: createdItems };
});
}
Nested Transactions
export async function bulkImportFlyers(flyersData: FlyerInput[]): Promise<ImportResult> {
return withTransaction(async (client) => {
const results = [];
for (const flyerData of flyersData) {
try {
// Each flyer import is atomic
const flyer = await createFlyerWithItems(
flyerData,
flyerData.items,
client, // Pass transaction client
);
results.push({ success: true, flyer });
} catch (error) {
results.push({ success: false, error: error.message });
}
}
return results;
});
}
Input Validation
ADR: ADR-003
Zod Schema Definition
// src/schemas/flyer.schemas.ts
import { z } from 'zod';
export const createFlyerSchema = z.object({
store_id: z.number().int().positive(),
image_url: z
.string()
.url()
.regex(/^https?:\/\/.*/),
start_date: z.string().datetime(),
end_date: z.string().datetime(),
items: z
.array(
z.object({
name: z.string().min(1).max(255),
price: z.number().positive(),
quantity: z.string().optional(),
}),
)
.min(1),
});
export type CreateFlyerInput = z.infer<typeof createFlyerSchema>;
Route Validation Middleware
import { validateRequest } from '../middleware/validation';
import { createFlyerSchema } from '../schemas/flyer.schemas';
app.post('/api/flyers', validateRequest(createFlyerSchema), async (req, res) => {
// req.body is now type-safe and validated
const flyer = await flyerService.createFlyer(req.body);
return sendSuccess(res, flyer, 'Flyer created successfully', 201);
});
Manual Validation
import { createFlyerSchema } from '../schemas/flyer.schemas';
export async function processFlyer(data: unknown): Promise<Flyer> {
// Validate and parse input
const validated = createFlyerSchema.parse(data);
// Type-safe from here on
return flyerDb.createFlyer(validated);
}
Authentication
ADR: ADR-048
Protected Route with JWT
import { authenticateJWT } from '../middleware/auth';
app.get(
'/api/profile',
authenticateJWT, // Middleware adds req.user
async (req, res) => {
// req.user is guaranteed to exist
const user = await userDb.getUserById(req.user.id);
return sendSuccess(res, user);
},
);
Optional Authentication
import { optionalAuth } from '../middleware/auth';
app.get(
'/api/flyers',
optionalAuth, // req.user may or may not exist
async (req, res) => {
const flyers = req.user
? await flyerDb.listFlyersForUser(req.user.id)
: await flyerDb.listPublicFlyers();
return sendSuccess(res, flyers);
},
);
Generate JWT Token
import jwt from 'jsonwebtoken';
import { env } from '../config/env';
export function generateToken(user: User): string {
return jwt.sign({ id: user.id, email: user.email }, env.JWT_SECRET, { expiresIn: '7d' });
}
Caching
ADR: ADR-029
Cache Pattern
import { cacheService } from '../services/cache.server';
export async function getFlyer(id: number): Promise<Flyer> {
// Try cache first
const cached = await cacheService.get<Flyer>(`flyer:${id}`);
if (cached) return cached;
// Cache miss - fetch from database
const flyer = await flyerDb.getFlyerById(id);
// Store in cache (1 hour TTL)
await cacheService.set(`flyer:${id}`, flyer, 3600);
return flyer;
}
Cache Invalidation
export async function updateFlyer(id: number, data: UpdateFlyerInput): Promise<Flyer> {
const flyer = await flyerDb.updateFlyer(id, data);
// Invalidate cache
await cacheService.delete(`flyer:${id}`);
await cacheService.invalidatePattern('flyers:list:*');
return flyer;
}
Background Jobs
ADR: ADR-036
Queue Job
import { flyerProcessingQueue } from '../services/queues.server';
export async function enqueueFlyerProcessing(flyerId: number): Promise<void> {
await flyerProcessingQueue.add(
'process-flyer',
{
flyerId,
timestamp: Date.now(),
},
{
attempts: 3,
backoff: {
type: 'exponential',
delay: 2000,
},
},
);
}
Process Job
// src/services/workers.server.ts
import { Worker } from 'bullmq';
const flyerWorker = new Worker(
'flyer-processing',
async (job) => {
const { flyerId } = job.data;
try {
// Process flyer
const result = await aiService.extractFlyerData(flyerId);
await flyerDb.updateFlyerWithData(flyerId, result);
// Update progress
await job.updateProgress(100);
return { success: true, itemCount: result.items.length };
} catch (error) {
logger.error('Flyer processing failed', { flyerId, error });
throw error; // Will retry automatically
}
},
{
connection: redisConnection,
concurrency: 5,
},
);
Related Documentation
- ADR Index - All architecture decision records
- TESTING.md - Testing patterns
- DEBUGGING.md - Debugging strategies
- Database Guide - Database patterns
- Coder Reference - Quick reference for AI agents