api
Backend
APIs, bases de datos, autenticación y buenas prácticas de servidor.
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
SERIALouUUIDcomo 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.