Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 012 — Rutas y parámetros de ruta#

⬅️ 011 · 📚 Parte 1 · 🎓 Clases · 013 ➡️ Parte 1 — Responder · Nivel 🟢 introductorio · Pista backendClase construida — 10 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Extraer del camino la parte variable. Es la operación más frecuente de cualquier API y donde aparece la primera decisión de diseño real: qué tanto sabe el enrutador sobre el valor que extrae.

📚 Resultados de aprendizaje#

  1. Declarar un segmento con nombre en diez frameworks.
  2. Explicar por qué el valor llega como texto y quién lo convierte.
  3. Reconocer qué frameworks pueden rechazar un segmento mal formado antes de llegar a tu código, y qué se gana con eso.

🧩 La situación#

GET /tareas/42 devuelve {"id":"42"}. GET /tareas/abc-123 devuelve {"id":"abc-123"}. GET /tareas no coincide con nada y responde 404.

El tercer caso importa más de lo que parece: una ruta con segmento obligatorio no coincide cuando el segmento falta. No es un error que tú manejes; es que la ruta no aplica.

🧮 El contrato#

Petición Respuesta
GET /tareas/42 200 · {"id":"42"}
GET /tareas/abc-123 200 · {"id":"abc-123"}
GET /tareas/1 content-type: application/json
GET /tareas/con%20espacio 200 · {"id":"con espacio"}
GET /tareas 404

El cuarto caso es el que separa una implementación correcta de una a medias: el valor llega decodificado. %20 es un espacio, y los diez frameworks lo resuelven sin que se lo pidas, porque lo exige el estándar de URI [rfc9110].

Especificación ejecutable en contrato.json.

<!-- generado: fichas -->

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Express framework web de Node.js (JavaScript) 2010 MIT OpenJS Foundation
Fastify framework web de Node.js (JavaScript/TypeScript) 2016 MIT OpenJS Foundation
FastAPI framework web de Python (Python) 2018 MIT proyecto independiente
Flask framework web de Python (Python) 2010 BSD-3-Clause Pallets Projects
Django framework web de Python (Python) 2005 BSD-3-Clause Django Software Foundation
Spring Boot framework de aplicación de JVM (Java) 2014 Apache-2.0 Broadcom/VMware y colaboradores
ASP.NET Core framework web de .NET (C#) 2016 MIT Microsoft y .NET Foundation
Laravel full-stack-framework de PHP (PHP) 2011 MIT proyecto independiente
Ruby on Rails full-stack-framework de Ruby (Ruby) 2004 MIT proyecto independiente
Gin framework web de Go (Go) 2014 MIT proyecto independiente

🔧 Express#

Definió el modelo de middleware encadenado que copiaron casi todos los frameworks de Node.js. Minimalista no significa biblioteca: posee el bucle de peticiones.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 Fastify#

Validación y serialización derivadas de JSON Schema, con un sistema de plugins con encapsulamiento explícito.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 FastAPI#

Deriva validación, serialización y documentación OpenAPI de las anotaciones de tipo. Demostró que el tipado opcional de Python podía ser infraestructura, no adorno.

Arrancarla suelta, sin el verificador:

PORT=3000 python -m uvicorn main:app --host 127.0.0.1 --port 3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
main.py código Python
requirements.txt dependencias de Python, una por línea, con versión fijada

🔧 Flask#

Microframework que dejó a la persona elegir ORM, validación y estructura. El contrapunto exacto de Django dentro del mismo lenguaje.

Arrancarla suelta, sin el verificador:

PORT=3000 python app.py

Qué hay dentro de su directorio:

Archivo Qué es
app.py código Python
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
requirements.txt dependencias de Python, una por línea, con versión fijada

🔧 Django#

Baterías incluidas: ORM, migraciones, panel de administración, autenticación y formularios. Su panel generado sigue siendo un argumento decisivo para productos internos.

Arrancarla suelta, sin el verificador:

PORT=3000 python app.py

Qué hay dentro de su directorio:

Archivo Qué es
app.py código Python
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
requirements.txt dependencias de Python, una por línea, con versión fijada

🔧 Spring Boot#

Autoconfiguración y servidor incrustado sobre Spring. Convirtió un framework famoso por su configuración XML en uno de arranque inmediato.

Preparar sus dependencias, dentro de su directorio:

mvn -q -B package -DskipTests

Arrancarla suelta, sin el verificador:

PORT=3000 java -jar target/clase-012-1.0.0.jar --server.port=3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
pom.xml manifiesto de Maven: el proyecto, su Java, sus dependencias y cómo se empaqueta
src/main/java/labs/Aplicacion.java código Java

🔧 ASP.NET Core#

Reescritura multiplataforma y de código abierto de la pila web de Microsoft. Sus API mínimas trajeron el estilo de los microframeworks al ecosistema .NET.

Preparar sus dependencias, dentro de su directorio:

dotnet build -c Release --nologo -v quiet

Arrancarla suelta, sin el verificador:

PORT=3000 dotnet run -c Release --no-build --urls http://127.0.0.1:3000

Qué hay dentro de su directorio:

Archivo Qué es
Clase012.csproj proyecto de .NET: el marco de destino y las dependencias
Program.cs código C#
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

🔧 Laravel#

El framework más usado de PHP: ORM Eloquent, migraciones, colas, programación de tareas, pruebas y un ecosistema comercial propio. Redefinió lo que se espera de la experiencia de desarrollo en el lenguaje.

Preparar sus dependencias, dentro de su directorio:

composer install --no-interaction --quiet

Arrancarla suelta, sin el verificador:

PORT=3000 php -S 127.0.0.1:3000 -t public

Qué hay dentro de su directorio:

Archivo Qué es
bootstrap/app.php arranque de Laravel: qué grupo de rutas, qué capas y qué manejo de errores
bootstrap/providers.php código PHP
composer.json manifiesto de Composer: la versión de PHP y las bibliotecas del proyecto
config/app.php código PHP
config/cache.php código PHP
config/session.php código PHP
config/view.php código PHP
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

🔧 Ruby on Rails#

Origen de «convención sobre configuración» y de las migraciones de base de datos tal como se entienden hoy. Casi todos los frameworks completos posteriores citan su influencia.

Preparar sus dependencias, dentro de su directorio:

bundle install --quiet

Arrancarla suelta, sin el verificador:

PORT=3000 bundle exec puma -b tcp://127.0.0.1:3000 config.ru

Qué hay dentro de su directorio:

Archivo Qué es
.bundle/config archivo del proyecto
Gemfile dependencias de Ruby
config.ru punto de entrada de Rack, el estándar de servidores de Ruby
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

🔧 Gin#

El framework HTTP más usado de Go: enrutado rápido y middleware, sobre la biblioteca estándar.

Preparar sus dependencias, dentro de su directorio:

go mod tidy

Arrancarla suelta, sin el verificador:

PORT=3000 go run main.go

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
go.mod módulo de Go: su nombre, la versión del lenguaje y sus dependencias
main.go código Go

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones#

Express · express/server.mjs#

app.get("/tareas/:id", (peticion, respuesta) => {
  respuesta.json({ id: peticion.params.id });
});

:id es la sintaxis más extendida —Express la popularizó y la copiaron muchos—. El valor siempre es texto: Express no sabe ni pregunta qué tipo esperas.

Fastify · fastify/server.mjs#

app.get("/tareas/:id", (peticion, respuesta) => {
  respuesta.send({ id: peticion.params.id });
});

Sintaxis idéntica. La diferencia aparece en la clase 013, cuando entran los esquemas.

FastAPI · fastapi/main.py#

@app.get("/tareas/{id}")
def obtener(id: str) -> dict[str, str]:
    return {"id": id}

Aquí pasa algo que no ocurre en Express: el nombre del segmento y el del argumento se emparejan, y la anotación de tipo se aplica. Si escribieras id: int, FastAPI convertiría "42" a 42 y respondería 422 ante "abc-123" sin que tú escribas una línea de validación.

En esta clase se declara str a propósito, para que el contrato sea el mismo que en los demás. Pero ese es el punto: el framework puede saber más.

Flask · flask/app.py#

@app.get("/tareas/<id>")
def obtener(id: str):
    return jsonify(id=id)

Flask tiene convertidores en la propia ruta: <int:id>, <uuid:id>, <path:resto>. La anotación de Python no hace nada aquí — decora, no valida.

Django · django/app.py#

urlpatterns = [path("tareas/<str:id>", obtener)]

<str:id> declara nombre y convertidor a la vez. Con <int:id>, una petición a /tareas/abc no coincidiría con esta ruta y Django devolvería 404 — no 422. Es una distinción fina y correcta: si el convertidor no aplica, la ruta no es esa.

Spring Boot · spring-boot/.../Aplicacion.java#

@GetMapping("/tareas/{id}")
public Map<String, String> obtener(@PathVariable("id") String id) {
    return Map.of("id", id);
}

El nombre va explícito en la anotación por una razón concreta: los nombres de los parámetros se pierden al compilar salvo que se active la opción de conservarlos. Escribirlo evita depender de la configuración del compilador.

ASP.NET Core · aspnet-core/Program.cs#

app.MapGet("/tareas/{id}", (string id) => Results.Json(new { id }));

El emparejamiento es por nombre, como en FastAPI, y el tipo del parámetro dirige la conversión: con (int id), /tareas/abc daría 400 automáticamente.

Laravel · laravel/routes/web.php#

Route::get('/tareas/{id}', function (string $id) {
    return response()->json(['id' => $id]);
});

Laravel inyecta los segmentos por orden de aparición, no por nombre. Con dos segmentos, cambiarlos de sitio en la URL cambia qué recibe cada argumento aunque los nombres coincidan.

Rails · rails/config.ru#

get "/tareas/:id" => "tareas#mostrar"

def mostrar
  render json: { id: params[:id] }
end

Rails mete en params los segmentos de ruta, la cadena de consulta y el cuerpo a la vez. Es cómodo y tiene un coste de seguridad: si no distingues de dónde viene cada valor, un cliente puede colar por la cadena de consulta algo que esperabas de la ruta. La clase 070 vuelve sobre esto.

Gin · gin/main.go#

motor.GET("/tareas/:id", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"id": c.Param("id")})
})

Gin usa un árbol de prefijos comprimido, así que emparejar no se vuelve más lento por tener más rutas registradas. Es una de las razones de su reputación de rapidez.

🔬 Comparación#

Framework Sintaxis ¿Puede validar el tipo en la ruta? Si no valida
Express :id no tú conviertes
Fastify :id con esquema tú conviertes
FastAPI {id} + anotación , por el tipo del argumento 422 automático
Flask <int:id> , por convertidor 404 si no coincide
Django <int:id> , por convertidor 404 si no coincide
Spring Boot {id} + @PathVariable , por el tipo 400 automático
ASP.NET Core {id} + tipo , por el tipo 400 automático
Laravel {id} con restricción where tú conviertes
Rails :id con restricción constraints tú conviertes
Gin :id no tú conviertes

La columna del medio revela el eje real de esta clase, y no es «cuál es mejor»:

Los frameworks tipados usan el tipo que ya escribiste. En FastAPI, Spring Boot y ASP.NET Core la validación es un efecto secundario de declarar el tipo del argumento — información que ibas a escribir de todas formas.

Y la respuesta al fallo no es la misma. Flask y Django devuelven 404: si el convertidor no aplica, esa ruta no es la tuya. FastAPI y Spring devuelven 422/400: la ruta era la correcta y el valor está mal. Las dos lecturas son defendibles y afectan al cliente, así que conviene elegir a conciencia.

✅ Verificación#

node scripts/run-class.mjs 012

⚠️ Errores frecuentes#

🧪 Reto de transferencia#

Cambia una implementación para que el identificador solo acepte dígitos, y decide si el fallo es 404 o 422. Añade el caso a contrato.json y ejecuta: las otras nueve deben fallar. Después argumenta tu elección de código en porque-si-porque-no.md.

🔗 Enlaces#

Fuentes#