Apéndice E — E. Estructura de proyecto ×3

El árbol de carpetas de Nexus comentado, por lenguaje

NotaEn una frase

La plantilla reusable: dónde vive cada capa (rutas, schemas, servicios, queries, middleware) en Node/TS, Python y Go. Mismos huesos, distinta sintaxis de carpetas — copia el árbol de tu stack y empieza.

E.1 TypeScript (Express)

nexus-api/
├── src/
│   ├── routes/          # HTTP edge: parse → call service → format response
│   │   ├── tasks.ts
│   │   └── auth.ts
│   ├── schemas/         # zod: the contract of each endpoint (input + output)
│   │   └── task.ts
│   ├── services/        # business rules — no req/res here, ever
│   │   └── taskService.ts
│   ├── db/
│   │   ├── pool.ts      # THE pool — created once, imported everywhere
│   │   └── queries.ts   # parametrized SQL lives here, not in services
│   ├── middleware/
│   │   ├── auth.ts      # requireUser — cookie → user → res.locals
│   │   └── errors.ts    # THE global handler — DomainError → {error:{code}}
│   ├── errors.ts        # DomainError hierarchy
│   └── app.ts           # wiring: middleware → routes → error handler LAST
├── migrations/          # dbmate: numbered, versioned, never edited
├── tests/               # vitest + supertest, DB real, rollback-per-test
├── .env.example         # every required key documented — the real .env is gitignored
├── Dockerfile           # multi-stage: deps → build → runtime
└── compose.yaml         # app + postgres for dev

E.2 Python (FastAPI)

nexus-api/
├── app/
│   ├── routers/         # APIRouter per resource: the HTTP edge
│   │   ├── tasks.py
│   │   └── auth.py
│   ├── schemas/         # pydantic: contract IS the framework's language
│   │   └── task.py
│   ├── services/        # business rules — never sees Request/Response
│   │   └── task_service.py
│   ├── db/
│   │   ├── pool.py      # connection pool — built at startup
│   │   └── queries.py   # parametrized SQL
│   ├── deps.py          # Depends: current_user, get_db — injection lives here
│   ├── errors.py        # exception hierarchy + handlers
│   └── main.py          # wiring: include_router + exception handlers
├── migrations/          # alembic: versions/, env.py
├── tests/               # pytest + TestClient + rollback fixture
├── .env.example
├── Dockerfile
└── compose.yaml

E.3 Go

nexus-api/
├── cmd/
│   └── api/
│       └── main.go      # THE entrypoint: config → pool → mux → server
├── internal/            # unimportable from outside — enforced by the compiler
│   ├── http/
│   │   ├── routes.go    # ServeMux patterns: "GET /tasks/{id}"
│   │   └── middleware.go # requireUser wrapping http.Handler
│   ├── tasks/
│   │   ├── service.go   # business rules
│   │   └── queries.go   # parametrized SQL
│   ├── auth/
│   └── errors/          # sentinel errors + the one mapper
├── migrations/          # goose: .sql files, up/down
├── tests/ or *_test.go  # go test + httptest, tx rollback
├── .env.example
├── Dockerfile           # multi-stage: builder → scratch/distroless binary
└── compose.yaml

E.4 Cómo leer los árboles

Las capas son las mismas, los nombres cambian. routes/routers/ http = el borde HTTP. schemas/schemas/structs en http = el contrato. services = las reglas. db/queries = el SQL. La arquitectura del cap. 7 no es “de Express” — es de backend.

Una query es la pregunta que le haces a la base de datos en SQL: SELECT * FROM tasks WHERE done = false. La BD traduce la pregunta a un plan de búsqueda — por eso los índices importan.

El SQL tiene casa propia. En los tres, las queries viven en un archivo/capa aparte (queries.ts, queries.py, queries.go) — no esparcidas por los servicios. Cuando el DBA pregunta “¿qué le pide tu app a la BD?”, la respuesta es un archivo, no un grep.

Una base de datos es el programa que guarda datos de forma permanente y los responde rápido. Relacional (Postgres): tablas con relaciones. No relacional: documentos, clave-valor, grafos — cada una para una forma de dato distinta.

internal/ en Go es el único muro real. El compilador prohíbe importarlo desde fuera — la privacidad que en los otros stacks es convención, en Go es ley.

El entrypoint es delgado en los tres. app.ts/main.py/main.go solo conectan piezas — si tiene lógica de negocio, el árbol falló.

E.5 Errores comunes

Error Por qué pasa Fix
Lógica en las rutas “Es un endpoint pequeño” La ruta parsea y delega — el servicio decide
SQL esparcido Queries dentro de servicios queries.* por dominio — un archivo por fuente de verdad
El pool recreado por request createPool() dentro del handler El pool nace una vez en el entrypoint y se inyecta
Carpetas por tipo técnico controllers/, models/ genéricos Por dominio (tasks/, auth/) — el proyecto crece por recursos
Tests en otra galaxia tests/ sin espejo del src El árbol de tests refleja el de src — encontrar el test = saber dónde

E.6 Mini reto

Arrancar un proyecto nuevo desde cero usando solo el árbol como guía.

El orden correcto: entrypoint delgado → pool inyectado → una ruta de ejemplo que recorre las 4 capas (ruta → schema → servicio → query) → error handler → un test de integración. Si en 30 minutos tienes GET /health + un endpoint CRUD completo con las capas separadas, el árbol funciona — y ese esqueleto es literalmente lo que un lead espera el día 1.

E.7 Vocabulario técnico del capítulo

Un bucle repite una acción por cada elemento o hasta cumplir una condición: for task in tasks hace algo con cada tarea. map y filter son bucles disfrazados de funciones — transforman/filtran colecciones sin for explícito.

La autenticación responde “¿quién eres?” — login, contraseña, token. Se confunde con autorización (“¿qué puedes hacer?”), que es la pregunta siguiente. Primero te identificas, luego te dejan o no pasar.

Una cookie es un dato que el servidor pone en tu navegador y el navegador devuelve en cada request. httpOnly = JavaScript no puede leerla (el XSS no la roba); Secure = solo viaja por HTTPS.

Un middleware es un filtro en la cadena del request: pasa por él antes de llegar a tu handler. Auth es middleware — “verifica el token” vive una vez y protege todas las rutas, no se copia en cada una.

Una variable de entorno es configuración que vive fuera del código: DATABASE_URL, JWT_SECRET. El mismo binario corre en dev y prod con distintas vars — los secretos nunca se escriben en el código.

Un servidor es una computadora que espera peticiones y las responde 24/7. Físicamente no es nada mágico: es una máquina (a veces una VM alquilada) corriendo tu programa, con la diferencia de que está siempre encendida y conectada.

Un dominio es el nombre que compras (midominio.com) y apuntas a tu servidor por DNS. Sin él, tus usuarios tendrían que memorizar una IP. El HTTPS serio requiere dominio.

Docker Compose describe un sistema de varios contenedores en un YAML: la app + Postgres + Redis, sus redes y volúmenes. docker compose up levanta el entorno completo de desarrollo con un comando.

Un rollback es volver atrás: la migración se revierte, el deploy regresa a la versión anterior. Todo cambio serio tiene plan de rollback antes de ejecutarse — si no, el plan es rezar.

Un health check es el endpoint /health donde la app dice “estoy viva”. El orquestador/load balancer lo consulta cada pocos segundos: si falla, reinicia la instancia o deja de mandarle tráfico.

El runtime es el motor que ejecuta tu código: Node.js es el runtime de JavaScript fuera del navegador; CPython es el de Python; Go compila a binario y el runtime va empaquetado dentro. Es “quien corre” lo que escribiste.

Un framework es un esqueleto de aplicación ya decidido: te da la estructura (rutas, validación, errores) y tú llenas la lógica. Diferencia con librería: la librería la llamas tú; el framework te llama a ti.

El build (construcción) es el proceso que convierte tu código fuente en algo ejecutable/entregable: compilar TypeScript a JS, empaquetar el frontend, armar la imagen Docker. Lo que se despliega es el resultado del build, no tu código crudo.

Un compilador traduce código a otra forma: TypeScript → JavaScript (transpila), Go → binario (compila). Atrapa errores antes de ejecutar. tsc, esbuild, go build son compiladores.

La memoria RAM es el escritorio de trabajo del programa: rápida, pero se borra al apagar. Los datos que deben sobrevivir van a disco o a la base de datos. Por eso let tasks = [] pierde todo al reiniciar.

El esquema es el plano de la base de datos: qué tablas hay, qué columnas tiene cada una y de qué tipo. Es contrato: una fila que no cumple el esquema no entra. Se cambia con migraciones, no a mano.

Una transacción es un grupo de operaciones que se confirman juntas o no se confirma ninguna: transferir dinero = restar de A y sumar a B. Si falla a la mitad sin transacción, el dinero desapareció.

Un pool es el staff de conexiones a la BD: abrir una conexión por request es caro, así que el pool mantiene ~10–20 abiertas y las presta. El request la usa, la devuelve, y la siguiente la reutiliza.

E.8 Lo que deberías saber hacer ahora