Skip to content
Development
Skill

/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.

From plugin
software-development-department
72116 skills28 agents1 MCP
Install
$ npx -y skills add tranhieutt/software_development_department --skill backend-patterns --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/backend-patterns

Context preview

The summary Claude sees to decide when to auto-load this skill.

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.

SKILL.md

backend-patterns.SKILL.md
name: backend-patterns
type: reference
description: "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."
paths: ["**/*.ts", "**/*.js", "**/server.*", "**/app.*", "**/routes/**"]
effort: 3
allowed-tools: Read, Glob, Grep, Write, Edit, Bash
context: fork
agent: backend-developer
user-invocable: true
when_to_use: "When building Node.js backend services with Express, Fastify, or similar frameworks"

Backend Patterns

Critical rules (non-obvious)

  • **Always handle async errors in Express**: unhandled promise rejections crash the process; use `express-async-errors` or wrap every async handler
  • **Never trust `req.body` size**: set `limit` on body-parser; default 100kb is too large for some, too small for others
  • **`process.env` access at import time**: if accessed before `dotenv.config()`, value is undefined; call config() first in entry file
  • **Connection pool misconfiguration**: default pool size (10) will exhaust under load; set `pool.max` based on `(num_cores * 2) + effective_spindle_count`
  • **`res.json()` after `res.send()`**: causes "Cannot set headers after they are sent" — always `return` after sending response

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 `express-async-errors` package | | `await` inside `forEach` | Use `Promise.all(array.map(async...))` | | Logging raw errors to client | Log internally; return sanitized message to client | | Missing `return` after `res.json()` | Always `return res.json(...)` to stop execution | | Secrets in `config.js` | Use `process.env` + validation on startup |

Read more
Ships withsoftware-development-department

Software Development Department

Get the whole plugin