🔧 Backend — fundamentos transversales
HTTP y diseño de APIs, autenticación y autorización, bases de datos y ORMs, colas y workers, caching, testing, seguridad y despliegue. El contenido completo que aplica a cualquier lenguaje backend.
Backend — fundamentos transversales
Un ingeniero backend profesional razona en términos de protocolos, garantías, estados y fallos, no de sintaxis. El lenguaje es una herramienta; estos conceptos son la carrera — y aplican igual a Go, Python, TypeScript o PHP.
Las wikis del sitio profundizan cada tema: HTTP, REST APIs, SQL, Transacciones, Índices, TDD, OWASP, Autenticación. Léelas en paralelo.
HTTP: el protocolo del backend
El backend es HTTP: recibe peticiones y devuelve respuestas. Dominar el protocolo por encima del framework te da ventaja en todo.
Estructura de una petición y una respuesta
PETICIÓN:
POST /api/usuarios HTTP/1.1
Host: api.misitio.com
Content-Type: application/json
Authorization: Bearer <token>
{"nombre": "Ana", "email": "ana@mail.com"}
RESPUESTA:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/usuarios/42
{"id": 42, "nombre": "Ana"}
Verbos y semántica
| Verbo | Uso | Idempotente |
|---|---|---|
| GET | Leer | Sí |
| POST | Crear / acción no idempotente | No |
| PUT | Reemplazar todo el recurso | Sí |
| PATCH | Actualizar parcialmente | No |
| DELETE | Borrar | Sí |
💡 Idempotencia: repetir la operación da el mismo resultado.
DELETE /api/usuarios/42dos veces → la segunda también “funciona” (404 o 204, pero no crea efectos nuevos). Es la propiedad que hace seguro reintentar.
Códigos de estado que debes saber de memoria
2xx éxito: 200 OK, 201 Created, 204 No Content
3xx redirec.: 301 (permanente), 304 (Not Modified, caché)
4xx cliente: 400 Bad Request, 401 Unauthorized, 403 Forbidden,
404 Not Found, 409 Conflict, 422 Unprocessable
5xx servidor: 500 Internal, 502 Bad Gateway, 503 Unavailable, 504 Timeout
⚠️ Error común: devolver 200 para errores de negocio («responde con
{error:...}»). Es un anti-patrón: rompe la semántica, el monitoreo y los clientes. Usa los códigos de verdad.
Cabeceras clave
| Cabecera | Para qué |
|---|---|
Content-Type |
Qué formato lleva el body (application/json…) |
Authorization |
Credenciales (Bearer token, Basic) |
Cache-Control |
Política de caché del cliente |
CORS |
Quién puede llamarte desde el navegador |
Location |
Dónde está el recurso creado (con 201) |
Set-Cookie |
Establecer una cookie |
HTTP/2 y HTTP/3
- HTTP/1.1: una petición por conexión (con keep-alive reutiliza, pero en serie).
- HTTP/2: multiplexa muchas peticiones por una conexión, comprime cabeceras, prioriza. Hoy el estándar.
- HTTP/3: sobre QUIC/UDP, sin bloqueo head-of-line, mejor en redes malas. El futuro.
REST y diseño de APIs
REST es un estilo de arquitectura: modelas el sistema como recursos (nombres) operados con verbos HTTP (acciones).
Principios
- Recursos con nombres (sustantivos, en plural):
/usuarios,/pedidos, NO/getUsuario. - Verbos = acciones HTTP:
GET /usuarios/42,POST /pedidos,DELETE /pedidos/7. - Estado en el servidor, representaciones en el cuerpo (JSON).
- Stateless: cada petición lleva todo lo que necesita (auth, contexto). No hay sesión en el servidor.
- Códigos de estado correctos.
- HATEOAS (opcional): la respuesta enlaza a las acciones posibles (hoy poco usado).
Buenas prácticas
GET /api/usuarios?page=2&limit=50 paginación
GET /api/usuarios/42 un recurso
POST /api/usuarios crear
PUT /api/usuarios/42 reemplazar
PATCH /api/usuarios/42 actualizar parcial
DELETE /api/usuarios/42 borrar
Versionado: /api/v1/usuarios (o cabecera)
Filtros: ?estado=activo&desde=2026-01-01
- Paginación: siempre en listas. Modelos:
limit/offset(simple),cursor(escalable, evita saltos). - Validación: el backend nunca confía en el cliente; valida todo.
- Documentación: OpenAPI (Swagger) — el contrato de la API, genera clientes y tests.
- Errores consistentes: mismo formato siempre.
{
"error": {
"code": "USUARIO_NO_ENCONTRADO",
"message": "No existe un usuario con id 42",
"status": 404
}
}
Autenticación y autorización
Autenticación = ¿quién eres? Autorización = ¿qué puedes hacer? Son cosas distintas.
Sesiones vs tokens
| Sesión (cookie) | Token (JWT) | |
|---|---|---|
| Estado | En el servidor | En el propio token (stateless) |
| Escalado | Hay que compartir sesiones | Cualquier servidor valida el token |
| Invalidar | Fácil (borrar sesión) | Difícil (esperar expiración) |
| Uso típico | Apps web clásicas | APIs, SPA, móviles |
JWT (JSON Web Token)
Un JWT es un token firmado con 3 partes: header.payload.signature.
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsInJvbGUiOiJhZG1pbiJ9.tjVA9rKuQkhZ...
└─── header ───┘ └──── payload ──────┘ └──── firma (firma HMAC/RSA) ────┘
import jwt
token = jwt.encode(
{"sub": "42", "role": "admin", "exp": 1735689600},
SECRETO, # la clave secreta NUNCA en el cliente
algorithm="HS256",
)
# El servidor verifica la firma en cada petición: si el token no es
# válido o está firmado con otra clave, la verificación falla.
⚠️ Reglas de oro del JWT: guarda el secreto solo en el servidor (variables de entorno), fija
expcorto, usasubpara el usuario, nunca pongas datos sensibles en el payload (está firmado pero no cifrado: cualquiera puede leerlo). JWT sobre cookieshttpOnlyes el estándar moderno de SPAs.
OAuth 2.0 y OIDC
OAuth 2.0 permite delegar acceso («login con Google»): el usuario autoriza a una app a acceder a sus datos sin darle la contraseña. OIDC (OpenID Connect) añade la capa de identidad (¿quién es?).
Usuario ──▶ App (login con Google)
│ "¿me das acceso?" (OAuth authorization flow)
▼
Google (authorization server) ──▶ emite access token
│
▼
App usa el token para llamar a la API de Google
Almacenar contraseñas: hash, nunca texto plano
import bcrypt
hash_pwd = bcrypt.hashpw(b"secreta123", bcrypt.gensalt())
# bcrypt/argon2: lentos A PROPÓSITO (hacen fuerza bruta inviable)
if bcrypt.checkpw(b"secreta123", hash_pwd):
print("ok")
⚠️ Nunca md5/sha1 para contraseñas (instantáneos de romper). Usa bcrypt, argon2 o scrypt (lentos por diseño, con salt automático).
Autorización: RBAC
RBAC (Role-Based Access Control): roles → permisos.
PERMISOS = {
"admin": {"usuarios": "crud", "pedidos": "crud"},
"editor": {"usuarios": "r", "pedidos": "crud"},
"lector": {"usuarios": "r", "pedidos": "r"},
}
def puede(rol, recurso, accion):
return accion in PERMISOS.get(rol, {}).get(recurso, "")
Bases de datos desde el backend
La teoría está en Fundamentos: BBDD. Aquí, lo que cambia al usarla desde un backend:
ORM vs SQL directo
| ORM (SQLAlchemy, Prisma, GORM, Eloquent) | SQL directo / query builder | |
|---|---|---|
| Productividad | Alta (objetos ↔ tablas) | Media |
| Control del SQL | Bajo | Total |
| Migraciones | Suelen traerlas | Manuales |
| Riesgo | N+1, queries ineficientes ocultas | SQL manual, más seguro en rendimiento |
💡 Usa ORM para el 90% y SQL crudo para el 10% crítico (queries complejas, reports). Y siempre conoce qué SQL genera tu ORM (log de queries activado).
El error N+1 (el clásico)
# MAL (N+1): 1 query para la lista + N queries (una por cada pedido)
usuarios = db.query(Usuario).all()
for u in usuarios:
print(u.pedidos) # ← dispara 1 query POR usuario
# BIEN: 1 query con JOIN / eager loading
usuarios = db.query(Usuario).options(joinedload(Usuario.pedidos)).all()
⚠️ Con 100 usuarios: 101 queries vs 1. Se nota brutalmente. Activa el log de queries de tu ORM y lo verás al instante.
Migraciones
Las migraciones versionan el esquema de la BBDD como tu código: cada cambio es un archivo ordenado, aplicable y reversible.
# Ejemplo conceptual de migración
class CreateUsuarios(Migration):
def up():
create_table("usuarios", id=PK, nombre=String, email=Unique)
def down():
drop_table("usuarios")
💡 Reglas: nunca modifiques el esquema a mano en producción; siempre por migración. Las migraciones se aplican en orden y se versionan con el código.
Conexiones y pooling
Cada petición no abre una conexión nueva (carísimo): los pools reutilizan conexiones.
[App] ──▶ [Pool de conexiones (10)] ──▶ [PostgreSQL]
pide ───────────────▶ usa una libre
devuelve ───────────▶ queda libre para otra
⚠️ Fuga de conexiones (leak): si olvidas cerrar la conexión en una ruta, el pool se agota y toda la app se bloquea («too many connections»). Los frameworks modernos lo gestionan solos; sé consciente al usar clientes raw.
Colas y workers: procesamiento asíncrono
No todo puede hacerse en la petición HTTP (mandar 10.000 emails, procesar un vídeo, generar un reporte). Ahí entran las colas: encolas la tarea, un worker la procesa en segundo plano.
[API] ──▶ [Cola (Redis/RabbitMQ/SQS)] ──▶ [Worker] ──▶ [BBDD]
responde al instante procesa en background
# Encolar (rápido, la petición responde ya)
cola.enqueue("procesar_video", video_id=123)
# Worker: procesa en su propio proceso
def procesar_video(video_id):
renderizar(video_id)
notificar(video_id)
| Concepto | Qué es |
|---|---|
| Cola | Buffer de tareas pendientes (Redis, RabbitMQ, SQS, Kafka) |
| Worker | Proceso que saca y procesa tareas |
| Retry | Reintentar tareas fallidas (backoff exponencial) |
| DLQ (dead-letter queue) | Cola donde van las tareas que fallan demasiado |
| Idempotencia | El worker debe tolerar que la tarea se procese 2 veces |
💡 Regla de oro de las colas: las tareas deben ser idempotentes (procesarlas dos veces = resultado igual). Los retries las re-ejecutan; si no, duplicas emails/pagos.
Caching en backend
La teoría de caché está en System Design. En backend, el día a día:
# Cache-aside con Redis
def obtener_usuario(uid):
cacheado = redis.get(f"user:{uid}")
if cacheado:
return json.loads(cacheado) # hit: devuelve la copia
usuario = db.query(Usuario).get(uid) # miss: lee de BBDD
redis.set(f"user:{uid}", json.dumps(usuario), ex=300) # llena + TTL
return usuario
| Patrón | Cómo | Cuándo |
|---|---|---|
| Cache-aside | App lee caché → miss → BBDD → llena | El estándar |
| Read-through | La caché carga sola | Contenido |
| Write-through | Escribe a ambos | Consistencia |
| Write-back | Escribe solo caché, vuelca luego | Alta escritura, riesgo |
⚠️ Invalidación: TTL corto para datos que cambian, invalidación manual al escribir para lo crítico. La caché es un acelerador, no una fuente de verdad.
Testing de backend
La teoría completa en TDD. El mapa:
| Nivel | Qué prueba | Velocidad | Ejemplo |
|---|---|---|---|
| Unit | Una función aislada | ms | suma(2,2) == 4 |
| Integration | Varias piezas juntas (ruta + BBDD) | s | crear usuario vía API |
| E2E | Flujo completo real | min | registro → login → pedido |
| Contract | La API cumple su contrato | s | OpenAPI/validación |
# Test unitario (pytest)
def test_suma():
assert suma(2, 2) == 4
# Test de API (FastAPI TestClient / httpx)
def test_crear_usuario(client):
r = client.post("/api/usuarios", json={"nombre": "Ana"})
assert r.status_code == 201
assert r.json()["id"] is not None
💡 Mocks vs real: unit tests con mocks (rápidos, aislados); integration tests con BBDD real (temporal o de test). No mockees el
SELECT: tu test dejará de validar el SQL real.
Seguridad: OWASP Top 10
Los 10 riesgos que todo backend debe mitigar (detalle en wiki: OWASP):
| Riesgo | Mitigación |
|---|---|
| Inyección SQL | Prepared statements / ORM (nunca concatenar strings) |
| Broken auth | bcrypt/argon2, sesiones seguras, MFA |
| XSS | Escapar salida, CSP, no usar innerHTML con datos |
| CSRF | Tokens CSRF en formularios, SameSite cookies |
| SSRF | Validar URLs de salida, bloqueo de rangos internos |
| Security misconfiguration | Sin defaults débiles, headers seguros, sin info en errores |
| Exposición de datos | Logs sin datos sensibles, permisos correctos |
# Inyección SQL: NUNCA hacer esto
query = f"SELECT * FROM usuarios WHERE email = '{email}'" # ⚠️
# Atacante: email = "x' OR '1'='1" → devuelve TODOS los usuarios
# SIEMPRE: parámetros
query = "SELECT * FROM usuarios WHERE email = ?"
cursor.execute(query, (email,))
💡 Regla de oro: nunca confíes en el cliente. Valida entradas, parametriza SQL, hashea contraseñas, no loguees datos sensibles, y pon headers de seguridad (
X-Frame-Options, CSP, HSTS).
Despliegue: 12-factor app
La metodología canónica para apps desplegables (en español en 12factor.net). Lo esencial:
- Config por entorno (variables de entorno, no en el código).
- Procesos stateless (cualquier instancia puede atender cualquier petición).
- Backing services (BBDD, Redis, colas) como servicios adjuntos reemplazables.
- Logs como streams (stdout, no archivos por app).
- Paridad dev/prod (lo que funciona en local, funciona en prod).
- Arranque rápido y apagado limpio.
import os
DATABASE_URL = os.getenv("DATABASE_URL", "postgres://localhost/app")
SECRET_KEY = os.getenv("SECRET_KEY") # ⚠️ nunca en el repo
Terminología que debes dominar
| Término | En una frase |
|---|---|
| Endpoint | Una ruta+verbo de tu API |
| Middleware | Código que corre antes/después de cada ruta (auth, logging) |
| DTO | Objeto de transferencia de datos (lo que cruza la red) |
| Serialización | Convertir objeto ↔ JSON |
| Idempotencia | Reintentar da el mismo resultado |
| Rate limiting | Limitar peticiones por usuario/IP |
| Pool de conexiones | Reutilizar conexiones a BBDD |
| N+1 | N queries extra por descuido del ORM |
| Migración | Cambio versionado del esquema |
| Worker | Proceso que procesa tareas de una cola |
| DLQ | Cola de tareas que fallaron demasiado |
| Cache invalidation | Marcar datos cacheados como viejos |
| AuthN / AuthZ | Autenticación / autorización |
| JWT | Token firmado y stateless |
| CORS | Política de acceso desde el navegador |
Práctica propuesta
- Construye una API REST CRUD en tu lenguaje (ver las rutas de Go, Python, TypeScript o PHP) con validación, paginación y códigos de estado correctos.
- Añade auth con JWT (login + endpoint protegido) y hashea contraseñas con bcrypt.
- Reproduce el error N+1 con tu ORM y arréglalo. Mide la diferencia en nº de queries.
- Crea una tarea de cola (envío de email simulado) con Redis: encolar, procesar, reintentar al fallar.
- Cachea una lectura caliente con Redis (cache-aside) y mide la mejora.
- Escribe tests (unit + integration) para tu API. Rompe algo a propósito y verifica que el test lo pilla.
- Documenta tu API con OpenAPI y generaa el cliente.
Para profundizar
- HTTP, REST APIs, Autenticación, TDD, OWASP: wikis del sitio.
- MDN — HTTP: la referencia en español.
- OWASP Top 10: los riesgos con mitigaciones.
- The Twelve-Factor App: la metodología en español.
- microservices.io: el catálogo de patrones.
- Fundamentos CS: BBDD, System Design.
- Sigue con 🎨 Frontend o elige tu lenguaje en Backend.