Starlette 1.0: tras 8 años, el motor de FastAPI es estable

Actualizado el 02/09/2026 — Este artículo fue actualizado con información reciente, secciones nuevas y referencias precisas a las versiones vigentes en producción.

Starlette 1.0.0 llegó el 22 de marzo de 2026, tras casi ocho años en versión 0.x, y cerró la era ZeroVer del framework ASGI más usado de Python. Hoy, a fines de agosto, la versión estable en producción es Starlette 1.3.1. El salto a semver estricto no fue cosmético: eliminó todas las APIs deprecadas, reestructuró el manejo de middleware y cambió cómo escribís tests. Para quienes trabajan con FastAPI, servidores MCP de Claude o cualquier infraestructura async en Python, este cambio determina si tu código sigue funcionando o necesita ajustes.

Starlette es un framework ASGI minimalista para Python que implementa routing, WebSockets, middleware y manejo de excepciones sin ORM ni validación de datos. Desde su creación en 2018 por Tom Christie, se convirtió en la base sobre la que construyen FastAPI (validación + OpenAPI) y el SDK oficial de Model Context Protocol para agentes de IA. Según el mantenedor Marcelo Trylesinski, el framework saltó de 57 millones a 325 millones de descargas mensuales en un año — infraestructura invisible que sostiene buena parte del ecosistema Python async en producción.

En 30 segundos

  • Starlette 1.0.0 salió el 22 de marzo de 2026 tras 8 años en 0.x: fin del ZeroVer, inicio de semver estricto
  • Última versión: 1.3.1, desde julio de 2026, con cero breaking changes en toda la línea 1.x
  • 325 millones de descargas mensuales (casi 10 millones por día) según el mantenedor
  • Requiere Python 3.10 o superior — no podés actualizar si seguís en 3.9 o inferior
  • Se instala con pip install starlette; si usás FastAPI, llega sola como dependencia obligatoria desde FastAPI 0.138+
  • APIs eliminadas: on_startup, on_shutdown, @app.route() — reemplazos: lifespan, add_route(), Router explícito
  • BaseHTTPMiddleware cambió internamente — la API externa es igual, pero algunos middlewares pueden romper
  • TestClient usa httpx en vez de requests — API similar pero no idéntica; afecta tests existentes
  • FastAPI 0.138+ y MCP SDK 0.7.0+ exigen Starlette 1.0+ obligatoriamente
  • Changelog oficial en starlette.dev/release-notes/ y código en GitHub (encode/starlette)

¿Cuál es la última versión de Starlette y qué significa cada número?

La última versión de Starlette es la 1.3.1, publicada en julio de 2026. Desde el corte de 1.0.0 el 22 de marzo, el proyecto sigue el semver estricto: el primer dígito (1) cambia solo con breaking changes, el segundo (3) sube con features compatibles, el tercero (1) sube con correcciones de bugs. Eso significa que podés saltar de 1.0.0 a 1.3.1 sin tocar tu código.

VersiónTipo de releaseFechaEstado
1.0.0Major: corte de estabilidad22/03/2026Publicada
1.0.1Patch: correcciones críticas (CVE-2026-48710)01/08/2026Disponible
1.1.0Minor: mejoras compatiblesMayo 2026Estable
1.2.0Minor: mejoras compatiblesJunio 2026Estable
1.3.0Minor: mejoras compatiblesJulio 2026Estable
1.3.1Patch: correcciones menoresJulio 2026Actual recomendada

Para verificar qué versión tenés instalada, corré pip show starlette en tu terminal. También podés consultarlo desde Python con import starlette; print(starlette.__version__).

¿Por qué Starlette dejó el ZeroVer en 2026?

Starlette abandonó el ZeroVer porque, con 325 millones de descargas mensuales, el ecosistema necesitaba garantías de estabilidad. ZeroVer es la práctica de quedarse en 0.x para evitar el compromiso del versionado semántico: cualquier minor (0.45 a 0.46) podía romper APIs sin aviso.

La RC1 salió el 23 de febrero de 2026 y la versión final 1.0.0 llegó el 22 de marzo. Marcelo Trylesinski, mantenedor principal, fue claro en el anuncio: esto no es un rewrite, es una declaración de estabilidad. Desde ahora, si algo rompe, sube la major (2.0). Si se agrega funcionalidad compatible, sube la minor (1.1). Si es un fix, sube el patch (1.0.1). Predecible.

El proceso contó con aportes de Adrian Garcia Badaracco (Pydantic), Alex Grönholm (async Python) y Sebastián Ramírez, mantenedor de FastAPI. Fue decisión consensuada de la comunidad: un framework con ese volumen de descargas merecía ese compromiso público.

¿Qué diferencia hay entre Starlette, FastAPI y Django?

Starlette es un framework ASGI minimalista; FastAPI es una capa construida encima de Starlette; y Django es un framework full-stack síncrono. Son tres herramientas distintas para tres problemas distintos, y entender la diferencia define qué tenés que actualizar en tu stack.

  • Starlette: framework async mínimo. HTTP, WebSockets, middleware y manejo de excepciones, todo ASGI nativo. Cero opinión sobre ORM, templates o estructura de proyecto. Control total, overhead mínimo.
  • FastAPI: constructor sobre Starlette. Agrega validación con Pydantic, documentación OpenAPI automática y convenciones que aceleran el desarrollo. Es Starlette + Pydantic + azúcar sintáctico.
  • Django: framework full-stack WSGI (síncrono). Incluye ORM, templates, admin, autenticación y sesiones. Ideal para sitios tradicionales donde la velocidad de desarrollo pesa más que la concurrencia.

Ojo con un dato clave: la mayoría de la gente que cree que usa “Starlette puro” en realidad está dentro de FastAPI. Starlette a solas es infraestructura que consumen otros proyectos: el MCP SDK, librerías internas y equipos que necesitan control absoluto. FastAPI es lo que ves en tutoriales y en startups que quieren una API REST rápido. Si querés contexto sobre el lenguaje, consultá nuestra cobertura de las novedades del compilador JIT en Python.

¿Por qué Starlette es async y qué ventaja tiene frente a Django?

Async permite que un solo proceso de Python atienda cientos de requests concurrentes en un único thread, sin crear un thread por request. Django en WSGI usa el modelo clásico: un thread por request. Con Starlette y ASGI, un event loop coordina todo usando I/O no bloqueante.

Ejemplo concreto: tu API recibe 100 requests que consultan una API externa que tarda 2 segundos. En Django necesitás 100 threads simultáneos, cada uno bloqueado esperando. En Starlette, un solo thread maneja las 100: mientras una espera I/O, otra procesa headers y otra envía respuesta. El event loop intercala todo automáticamente.

  • Consumo de memoria: Starlette usa alrededor de 10 veces menos memoria que Django con la misma carga concurrente. Un thread ocupa unos 8 MB; una tarea async, unos 50 KB.
  • Latencia bajo carga: una request no espera a que otra termine. En Django, si hay 50 threads esperando I/O, la request 51 queda en cola. En Starlette, las 51 avanzan juntas.
  • Desventaja: complejidad mental. Async exige entender await, event loops y sus trampas, como nunca bloquear el loop. Django es más intuitivo si venís de PHP o de desarrollo síncrono.
  • Cuándo Django sigue siendo válido: para un blog, un CMS o una app monolítica con admin, Django sigue siendo la opción madura. Sin concurrencia masiva, el overhead de threads no molesta.

Alerta de seguridad (01/08/2026 — 02/09/2026): CVE-2026-48710 (“BadHost”) afectó todas las versiones anteriores a 1.0.1 y permite eludir autenticación manipulando el header HTTP Host. El parche lleva más de un mes disponible sin cambios de código requeridos. Si corrés FastAPI con agentes de IA o endpoints protegidos, actualizá a 1.0.1+ sin esperar.

¿Qué APIs fueron eliminadas en Starlette 1.0?

Starlette 1.0 eliminó todo lo que venía marcado como deprecado en la serie 0.x. Algunas de estas APIs las usaba muchísima gente en producción. Si todavía estás en 0.x y querés actualizar, esta tabla es tu mapa de reemplazos exacto.

API eliminadaReemplazoImpacto
on_startup / on_shutdownContexto lifespanAlto — muy usado en FastAPI
@app.route() / @app.websocket_route()app.add_route() o Router explícitoMedio — común en tutoriales
add_event_handler(“startup”)Contexto lifespanMedio
TestClient basado en requestsTestClient basado en httpxAlto en tests
Jinja2Templates sin jinja2 instaladopip install jinja2 explícitoBajo
Python < 3.10Actualizar a Python 3.10+Variable según proyecto

El cambio con mayor impacto práctico no está en la tabla: es BaseHTTPMiddleware. Merece su propia sección porque afecta a miles de proyectos en producción.

¿Cómo cambió BaseHTTPMiddleware en Starlette 1.0?

BaseHTTPMiddleware se reestructuró por dentro en Starlette 1.0: la API externa se mantiene igual, pero los middlewares que modifican headers, capturan excepciones o usan state pueden necesitar ajustes. Para la mayoría de los casos, el código sigue funcionando sin cambios.

El problema en 0.x: si extendías de BaseHTTPMiddleware, podías hacer casi cualquier cosa en dispatch(): bloquear la request, modificarla, rechazarla o generar respuestas custom. Funcionaba, pero internamente era ineficiente en async.

En Starlette 1.0: el flujo interno se reorganizó para mejorar el rendimiento. La clase sigue igual externamente, pero revisá tu middleware si hace algo de esto:

  • Modifica headers de request o response: puede requerir ajuste si las modificaciones interactúan de forma inesperada con otro middleware.
  • Captura excepciones en dispatch: el stack de manejo de errores cambió. Algunos try/except pueden no atrapar lo que atrapaban en 0.x.
  • Usa state de forma no convencional: la propagación de state entre capas es más estricta. Lo que funcionaba por efectos secundarios puede fallar en silencio.
  • Bloquea la request (valida o rechaza): ahora tenés que ser explícito con la respuesta. En 0.x el sistema a veces lo infería; en 1.0, no.

La solución más robusta es migrar a middleware ASGI puro: escribirlo directamente a nivel ASGI en vez de extender BaseHTTPMiddleware. Es un poco más verbose, pero te da control total y no depende de la implementación interna de Starlette.

Ejemplo: middleware de autenticación en 0.x vs 1.0

En Starlette 0.x, un middleware de autenticación típico se veía así:

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse

class AuthMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        token = request.headers.get("authorization")
        if not token:
            return JSONResponse({"error": "no token"}, status_code=401)
        request.state.token = token
        response = await call_next(request)
        return response

En Starlette 1.0, ese código probablemente siga funcionando. Pero para garantizar robustez en producción, reescribilo como middleware ASGI puro:

from starlette.responses import JSONResponse

async def auth_middleware(app, scope, receive, send):
    if scope["type"] != "http":
        await app(scope, receive, send)
        return

    headers = dict(scope.get("headers", []))
    token = headers.get(b"authorization", b"").decode()

    if not token:
        response = JSONResponse({"error": "no token"}, status_code=401)
        await response(scope, receive, send)
        return

    scope["state"] = {"token": token}
    await app(scope, receive, send)

Es más explícito y no depende de la abstracción BaseHTTPMiddleware. Así lo agregás a tu app:

from starlette.middleware import Middleware

app = Starlette(
    middleware=[
        Middleware(auth_middleware)
    ]
)

¿Cómo instalar Starlette con pip?

pip install starlette descarga siempre la última versión estable desde PyPI, hoy la 1.3.1. Lo importante es hacerlo en el entorno correcto y fijar bien las versiones en tus dependencias.

  • Creá primero un entorno virtual. Así probás sin romper nada: python -m venv venv y después source venv/bin/activate (Linux/Mac) o venv\Scripts\activate (Windows).
  • Instalá el paquete. pip install starlette trae la última estable. Verificá con pip show starlette qué versión quedó instalada.
  • Fijá la versión en requirements.txt. Usa un rango como starlette>=1.3,<2.0: recibís fixes automáticos pero ninguna major sorpresiva.
  • Si usás FastAPI, no la instales aparte. pip install fastapi arrastra Starlette 1.x como dependencia obligatoria desde FastAPI 0.138+. Dejá que el resolutor gestione la versión compatible.
  • Confirmá tu versión de Python. Starlette 1.0 requiere 3.10 o superior. Con 3.9 o inferior, la instalación falla.

Un detalle: si usás Jinja2Templates, jinja2 debe estar explícito en tus dependencias. En 0.x se instalaba de forma opcional; en 1.0 el paquete base no lo incluye.

¿Cómo impacta Starlette 1.0 a los proyectos FastAPI?

FastAPI hereda directamente de Starlette, así que cada cambio baja por herencia a tu app FastAPI. Desde junio de 2026, FastAPI 0.138+ exige Starlette 1.0+ como dependencia obligatoria: si hacés pip install --upgrade fastapi, Starlette 1.x baja automático.

Donde sí te puede pegar es en tu propio código. Si usás on_startup u on_shutdown directamente, tenés que migrar a lifespan. Ejemplo de lo viejo:

@app.on_event("startup")
async def startup():
    # abrir conexión a DB
    db.connect()

@app.on_event("shutdown")
async def shutdown():
    # cerrar conexión
    db.close()

Lo nuevo, compatible con Starlette 1.0+ y FastAPI actual:

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app):
    # startup: abrir conexión a DB
    db.connect()
    yield
    # shutdown: cerrar conexión
    db.close()

app = FastAPI(lifespan=lifespan)

Antes de hacer pip install --upgrade fastapi, corré tu suite de tests. Si tenés middleware personalizado, revisá la sección de BaseHTTPMiddleware. FastAPI 0.138+ es compatible; versiones anteriores pueden mostrar incompatibilidades menores.

¿Por qué el MCP SDK de Python requiere Starlette 1.0?

El Model Context Protocol SDK en Python (MCP SDK), el kit con el que se construyen servidores MCP para Claude y otros agentes de IA, exige Starlette 1.0+ desde su versión 0.7.0 publicada en 2026. Si construís herramientas para Claude, agentes con OpenAI o cualquier sistema que exponga capacidades vía MCP, tu servidor corre sobre Starlette 1.0 por debajo.

Tener semver estricto acá no es cosmético. Significa que podés depender del framework en producción sin que un bump de versión rompa la integración con tu servidor MCP. Para equipos que despliegan agentes de IA en infraestructura Python, esa previsibilidad vale mucho.

  • MCP SDK 0.7.0+ requiere Starlette 1.0: validación de que la comunidad de IA confía en esta línea para producción.
  • Cada servidor MCP corre sobre Starlette internamente: no es opcional ni abstracto. Es el servidor HTTP/WebSocket de abajo.
  • Implicancia directa: si desplegás servidores MCP en VPS, contenedores o la cloud, ya estás en Starlette 1.x de facto.

¿Qué se mejoró en Starlette entre 1.0.0 y 1.3.1?

Entre Starlette 1.0.0 y 1.3.1 no hubo ni un solo breaking change: solo fixes y optimizaciones acumuladas a lo largo de cuatro meses. Los cambios no son features llamativas, pero importan en producción.

  • WebSocket close reason (desde 1.0): podés rechazar una conexión WebSocket con un motivo HTTP (403, 401) antes de aceptarla. En 0.x había que abrir la conexión y cerrarla adentro.
  • MultiPart parser más estricto (desde 1.0): si un cliente mandaba mil campos duplicados en un form, el parser acumulaba todos. Ahora respeta el límite de campos explícitamente.
  • Session change tracking (desde 1.0): las sesiones saben si fueron modificadas en la request actual. Permite no regrabar sesiones intactas y optimizar cache.
  • Fixes acumulados en 1.1 a 1.3: correcciones de edge cases en routing, mejor manejo de excepciones en lifespan y optimizaciones de memoria en el event loop.
  • Documentación mejorada: las release notes de cada versión desde 1.0 son mucho más detalladas que en la era 0.x, lo que facilita cada actualización.

¿Cómo actualizar un proyecto existente a Starlette 1.0?

Actualizar a Starlette 1.0 no es solo pip install --upgrade: el upgrade impacta todo lo que depende del framework. Estos ocho pasos evitan la mayoría de los problemas comunes.

  • Paso 1: verificá tu versión de Python. Corré python --version. Necesitás 3.10 o superior. Si estás en 3.9, actualizá el intérprete primero.
  • Paso 2: creá un entorno virtual limpio. Acá probás sin romper nada: python -m venv test_env + source test_env/bin/activate (Mac/Linux) o test_env\Scripts\activate (Windows).
  • Paso 3: buscá on_startup y on_shutdown en tu código. Terminal: grep -r "on_startup\|on_shutdown" . Anotá los archivos donde aparecen.
  • Paso 4: actualizá por pasos. Con FastAPI: pip install --upgrade fastapi starlette. Con Starlette puro: pip install --upgrade "starlette>=1.0".
  • Paso 5: corré tu suite de tests. pytest o tu runner habitual. La mayoría de los fallos vienen del TestClient o del lifespan.
  • Paso 6: verificá Jinja2 si usás templates. pip install jinja2 debe estar explícito en requirements.txt. En 0.x era opcional.
  • Paso 7: probá manualmente en local. Abrí http://localhost:8000 si es una API. Hacé un par de requests y mirá los logs.
  • Paso 8: deploy escalonado. Esperá un ciclo de testing (1-2 días), pasá por staging y recién ahí tocá producción.

Guía paso a paso para migrar tu proyecto a Starlette 1.0

No alcanza con hacer upgrade: hay que revisar el código manualmente. Acá va el checklist con los cinco frentes de migración y ejemplos reales de antes y después.

Paso 1: migrar on_startup y on_shutdown a lifespan

Si tu app FastAPI o Starlette pura usa @app.on_event("startup") o add_event_handler, eso está deprecado. Reemplazalo con un context manager lifespan. Lo viejo:

@app.on_event("startup")
async def startup():
    global db
    db = await Database("postgres://...").connect()
    print("DB conectada")

@app.on_event("shutdown")
async def shutdown():
    await db.disconnect()
    print("DB desconectada")

Lo nuevo en 1.0+:

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app):
    # startup code
    global db
    db = await Database("postgres://...").connect()
    print("DB conectada")
    yield
    # shutdown code
    await db.disconnect()
    print("DB desconectada")

app = FastAPI(lifespan=lifespan)

El código después del yield corre al cerrar la app. Es más explícito y funciona exactamente igual.

Paso 2: reemplazar @app.route() y @app.websocket_route()

Si usás los decoradores @app.route(), migra a app.add_route() o a un Router explícito. Lo viejo:

@app.route("/users", methods=["GET"])
async def get_users():
    return {"users": []}

Lo nuevo, con add_route:

async def get_users():
    return {"users": []}

app.add_route("/users", get_users, methods=["GET"])

O mejor aún, con un Router declarativo:

from starlette.routing import Route

routes = [
    Route("/users", get_users, methods=["GET"])
]

app = Starlette(routes=routes)

Paso 3: validar el TestClient en tus tests

El TestClient cambió de requests a httpx internamente, pero la API es casi igual. El código básico queda idéntico:

from starlette.testclient import TestClient

client = TestClient(app)
response = client.get("/users")
assert response.status_code == 200
assert response.json() == {"users": []}

Si tenés assertions específicas sobre headers o cookies (como “set-cookie”), probá primero. La API de httpx es parecida pero no idéntica a la de requests, y esas diferencias aparecen justo en los detalles.

Paso 4: revisar middleware personalizado

Si tenés middleware que extiende BaseHTTPMiddleware, probá actualizar Starlette primero. Nueve de cada diez veces funciona sin cambios. Si ves comportamiento raro (excepciones no capturadas, headers sin modificar, state perdido), reescribilo como middleware ASGI puro, como mostramos en la sección de BaseHTTPMiddleware.

Paso 5: correr tests y validar en staging

Si tu suite pasa, llevá el cambio a un entorno staging o dev antes de tocar producción. Dejalo corriendo 24-48 horas, monitoreá logs buscando excepciones nuevas y recién después deploy a producción. Ese colchón es lo que separa una migración aburrida de un incidente.

¿Cómo migrar a Starlette 1.0 si vengo de la versión 0.50?

Si venís de Starlette 0.50, la migración a 1.0 pasa por los mismos cuatro frentes que desde cualquier versión 0.x: lifespan, decoradores de rutas, TestClient y middleware. La 0.50 pertenece a la serie 0.x, así que todas las eliminaciones de la tabla de arriba aplican a tu código.

La buena noticia: la serie 0.x acumuló años de avisos de deprecación. Si mantenías tus dependencias al día, probablemente ya dejaste de usar las APIs removidas. El checklist concreto:

  • Identificá tu punto de partida exacto. Corré pip show starlette y revisá requirements.txt. Saber si estás en 0.50.x o más atrás define cuánta deuda arrastrás.
  • Buscá las APIs removidas con grep. grep -rn "on_startup\|on_shutdown\|add_event_handler\|@app.route\|websocket_route" . Cada resultado es una tarea concreta.
  • Migrá el ciclo de vida a lifespan. Seguí el paso 1 de la guía de arriba, con el context manager y el yield.
  • Reemplazá los decoradores de rutas. Objetos Route o add_route(), según prefieras estilo declarativo o imperativo.
  • Ajustá las assertions del TestClient. Sobre todo si chequean headers o cookies con la API vieja de requests.
  • Probá tus middlewares en dev. Cualquier comportamiento raro con state, headers o excepciones apunta a BaseHTTPMiddleware.
  • Subí de a un entorno por vez. Dev, staging 24-48 horas, producción. Nunca todo junto.

En un proyecto mediano, esta migración es trabajo de horas, no de semanas: los cambios son mecánicos y los reemplazos son uno a uno. El riesgo real se concentra en los middlewares personalizados y en los tests con assertions estrictas.

¿Dónde encontrar el changelog oficial de Starlette?

El changelog oficial de Starlette está publicado en starlette.dev/release-notes/ y el código fuente en GitHub en github.com/encode/starlette. Desde que saltó a versionado semántico el 22 de marzo de 2026, cada release tiene notas detalladas sobre fixes, cambios internos y migraciones recomendadas.

Si necesitás contexto sobre un problema específico con una versión, GitHub tiene los issues etiquetados por versión y el archivo CHANGELOG.md en la raíz del repo trae un historial completo de cambios. Cada versión 1.x mantiene un resumen de qué se agregó, qué se arregló y si hay cambios en el comportamiento observable.

Te puede interesar...