api

Backend

APIs, databases, authentication and server best practices.

REST APIs

REST é um estilo arquitetural para sistemas distribuídos baseado em recursos, verbos HTTP e statelessness.

  • GET :: leitura, idempotente, cacheável
  • POST :: criação, não idempotente
  • PUT :: substituição total, idempotente
  • PATCH :: atualização parcial
  • DELETE :: remoção, idempotente
src/routes/users.ts (Express)
import { Router } from "express";
import { body, param, validationResult } from "express-validator";

const router = Router();

// GET /users/:id
router.get("/:id", param("id").isUUID(), async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });

  const user = await userService.findById(req.params.id);
  if (!user) return res.status(404).json({ error: "user_not_found" });

  return res.json({ data: user });
});

// POST /users
router.post(
  "/",
  body("email").isEmail().normalizeEmail(),
  body("name").trim().isLength({ min: 2, max: 100 }),
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });

    const user = await userService.create(req.body);
    return res.status(201).json({ data: user });
  }
);

export default router;

💡 Dica

Use HTTP status codes corretamente: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 422 Unprocessable, 500 Internal Server Error.

Autenticação & JWT

JWT (JSON Web Token) é um padrão para transmitir claims de forma segura entre partes. Estrutura: header.payload.signature.

src/lib/auth.ts
import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";

const SECRET = process.env.JWT_SECRET!;

type TokenPayload = {
  sub: string;   // user id
  email: string;
  role: string;
};

// Gerar token (access + refresh)
export function signAccessToken(payload: TokenPayload): string {
  return jwt.sign(payload, SECRET, { expiresIn: "15m" });
}

export function signRefreshToken(userId: string): string {
  return jwt.sign({ sub: userId }, SECRET, { expiresIn: "7d" });
}

// Verificar token
export function verifyToken(token: string): TokenPayload {
  return jwt.verify(token, SECRET) as TokenPayload;
}

// Hash de senha
export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 12);
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}
src/middleware/auth.ts
import { Request, Response, NextFunction } from "express";
import { verifyToken } from "../lib/auth";

export function requireAuth(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "missing_token" });
  }

  try {
    const token = header.slice(7);
    const payload = verifyToken(token);
    req.user = payload; // extend Request type
    next();
  } catch {
    return res.status(401).json({ error: "invalid_token" });
  }
}

export function requireRole(role: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (req.user?.role !== role) {
      return res.status(403).json({ error: "insufficient_permissions" });
    }
    next();
  };
}

⚠️ Atenção

Nunca armazene JWT em localStorage. Prefira httpOnly cookies para tokens de sessão longa. Access tokens curtos (15m) + refresh tokens com rotação.

Bancos de Dados

PostgreSQL :: Boas práticas

  • Use SERIAL ou UUID como PK, prefira UUID para distribuição
  • Índices nos campos de busca frequente, mas não exagere
  • Transações para operações que precisam de consistência ACID
  • Connection pooling (PgBouncer, pg pool) em produção
  • Migrations versionadas (Flyway, Liquibase, Knex, Prisma migrate)
migrations/001_create_users.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE users (
  id           UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  email        TEXT NOT NULL UNIQUE,
  name         TEXT NOT NULL,
  password_hash TEXT NOT NULL,
  role         TEXT NOT NULL DEFAULT 'user',
  created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users (email);

-- Trigger para atualizar updated_at automaticamente
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER users_updated_at
  BEFORE UPDATE ON users
  FOR EACH ROW EXECUTE FUNCTION set_updated_at();
src/lib/db.ts (com Drizzle ORM)
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool);

// Schema tipado
export const users = pgTable("users", {
  id:           uuid("id").primaryKey().defaultRandom(),
  email:        text("email").notNull().unique(),
  name:         text("name").notNull(),
  passwordHash: text("password_hash").notNull(),
  role:         text("role").notNull().default("user"),
  createdAt:    timestamp("created_at", { withTimezone: true }).defaultNow(),
});

// Query type-safe
export async function findUserByEmail(email: string) {
  return db.select().from(users).where(eq(users.email, email)).limit(1);
}

Docker

Dockerfile (Node.js multi-stage)
# Estágio 1: dependências
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# Estágio 2: build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Estágio 3: runtime (imagem mínima)
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json .

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

EXPOSE 3000
CMD ["node", "dist/server.js"]
docker-compose.yml
version: "3.9"
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "user", "-d", "mydb"]
      interval: 5s
      retries: 5

volumes:
  pgdata:

AWS

S3 + CloudFront (este site)

Sites estáticos servidos globalmente com latência mínima. Custo quase zero para tráfego moderado.

Serviços comuns

  • EC2 + ALB + Auto Scaling :: workloads com estado, requisitos de compute personalizados
  • ECS Fargate :: containers sem gerenciar instâncias
  • Lambda :: funções event-driven, escalas a zero
  • RDS :: PostgreSQL/MySQL gerenciado com backups e réplicas
  • ElastiCache :: Redis gerenciado para cache e sessões
  • SQS/SNS :: mensageria desacoplada entre serviços
  • IAM :: princípio do menor privilégio, roles por workload

📝 Nota

Prefira roles IAM a credenciais de longa duração. Use aws-vault ou SSO localmente. Habilite MFA nas contas root e admin.