Receber por email
← Voltar para o catálogo

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.

67estrelas
Atualizado há 5 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add tranhieutt/software_development_department

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/backend-patterns-tranhieutt/svg)](https://www.skillteca.com.br/skills/backend-patterns-tranhieutt)

Skills relacionadas

Ver todas de Automação →

Alerta por categoria

Receba novas skills de Automação toda segunda

1 email curto, só com as skills novas de Automação. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

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 |

Compartilhar X LinkedIn