Software_development_department backend-patterns
Applies production backend patterns: middleware, error handling, auth, database integration, and API design. Use when working with backend service files or when the user mentions Express, Fastify, NestJS, backend patterns, or service architecture.
install
source · Clone the upstream repo
git clone https://github.com/tranhieutt/software_development_department
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/tranhieutt/software_development_department "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/backend-patterns" ~/.claude/skills/tranhieutt-software-development-department-backend-patterns && rm -rf "$T"
manifest:
.claude/skills/backend-patterns/SKILL.mdsource content
Backend Patterns
Critical rules (non-obvious)
- Always handle async errors in Express: unhandled promise rejections crash the process; use
or wrap every async handlerexpress-async-errors - Never trust
size: setreq.body
on body-parser; default 100kb is too large for some, too small for otherslimit
access at import time: if accessed beforeprocess.env
, value is undefined; call config() first in entry filedotenv.config()- Connection pool misconfiguration: default pool size (10) will exhaust under load; set
based onpool.max(num_cores * 2) + effective_spindle_count
afterres.json()
: causes "Cannot set headers after they are sent" — alwaysres.send()
after sending responsereturn
Express: production setup
import express from "express"; import "express-async-errors"; // patches async error handling globally import helmet from "helmet"; import { rateLimit } from "express-rate-limit"; const app = express(); app.use(helmet()); app.use(express.json({ limit: "10kb" })); app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 })); // Routes app.use("/api/v1/users", userRouter); app.use("/api/v1/products", productRouter); // 404 handler — must come after all routes app.use((req, res) => res.status(404).json({ error: "Not found" })); // Global error handler — must have 4 params app.use((err: Error, req: Request, res: Response, next: NextFunction) => { const status = err instanceof AppError ? err.statusCode : 500; res.status(status).json({ error: err.message }); });
Repository pattern
interface IUserRepository { findById(id: string): Promise<User | null>; findByEmail(email: string): Promise<User | null>; save(user: User): Promise<User>; delete(id: string): Promise<void>; } class PgUserRepository implements IUserRepository { constructor(private readonly db: Pool) {} async findById(id: string) { const { rows } = await this.db.query( "SELECT * FROM users WHERE id = $1 AND deleted_at IS NULL", [id] ); return rows[0] ?? null; } }
Service layer with error types
class AppError extends Error { constructor(public message: string, public statusCode: number) { super(message); } } class NotFoundError extends AppError { constructor(msg: string) { super(msg, 404); } } class ForbiddenError extends AppError { constructor(msg: string) { super(msg, 403); } } class UserService { async getUser(id: string, requesterId: string): Promise<User> { const user = await this.repo.findById(id); if (!user) throw new NotFoundError(`User ${id} not found`); if (user.id !== requesterId && !isAdmin(requesterId)) throw new ForbiddenError("Access denied"); return user; } }
JWT middleware
import jwt from "jsonwebtoken"; export function authenticate(req: Request, res: Response, next: NextFunction) { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ error: "No token" }); try { req.user = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload; next(); } catch { res.status(401).json({ error: "Invalid token" }); } }
Database connection with retry
import { Pool } from "pg"; const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20, // pool size idleTimeoutMillis: 30_000, connectionTimeoutMillis: 2_000, }); // Test connection on startup async function connectWithRetry(retries = 5, delay = 2000) { for (let i = 0; i < retries; i++) { try { await pool.query("SELECT 1"); console.log("DB connected"); return; } catch (err) { if (i === retries - 1) throw err; await new Promise(r => setTimeout(r, delay * (i + 1))); // exponential backoff } } }
Graceful shutdown
const server = app.listen(PORT); async function shutdown(signal: string) { console.log(`${signal} received. Shutting down gracefully.`); server.close(async () => { await pool.end(); // drain DB connections process.exit(0); }); setTimeout(() => process.exit(1), 10_000); // force exit after 10s } process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT"));
Common pitfalls
| Pitfall | Fix |
|---|---|
| Async handler without try/catch | Use package |
inside | Use |
| Logging raw errors to client | Log internally; return sanitized message to client |
Missing after | Always to stop execution |
Secrets in | Use + validation on startup |