Lánzate a Python FastAPI: tu primer paso concreto hoy
Python FastAPI: lo esencial en un artículo — código real, esquemas y pasos concretos, extractos de un curso de 32 lecciones.
La mejor forma de aprender Python FastAPI es practicando. Este artículo te ayuda a empezar con extractos prácticos extraídos de un curso de 32 lecciones — para obtener un primer resultado hoy mismo.
- Introducción e instalación
- Rutas y métodos
- Pydantic y validación
- Inyección de dependencias
- Base de datos
¿Por qué FastAPI?
FastAPI en 1 frase
Framework Python moderno para construir APIs REST rápidas, tipadas y con documentación automática, basado en Starlette (ASGI) y Pydantic.
Los 4 superpoderes
| Poder | Detalle |
|---|---|
| Rendimiento | Async nativo vía ASGI/uvicorn, comparable a Node/Go |
| Seguridad de tipos | Type hints de Python -> validación en runtime vía Pydantic |
| Documentación automática | Swagger UI + ReDoc generados automáticamente |
| Experiencia de desarrollo | Autocompletado en IDE, errores claros, dependencias inyectables |
Comparación Flask vs FastAPI vs Django
| Criterio | Flask | FastAPI | Django |
|---|---|---|---|
| Estilo | Micro | Micro (enfocado en API) | Full-stack |
| Async nativo | No (extensiones) | SÍ | Parcial (DRF no) |
| Type hints | No | SÍ (Pydantic) | No |
| Documentación automática | No | SÍ (Swagger) | Vía DRF (manual) |
| ORM incluido | No | No | SÍ (Django ORM) |
| Aprendizaje | Muy fácil | Fácil | Moderado |
| Rendimiento (req/s) | ~5k | ~20-30k | ~3k |
Pequeño ejemplo: "Hello World"
# Flask from flask import Flask app = Flask(__name__) @app.route("/items/<int:item_id>") def get_item(item_id): return {"id": item_id}
# FastAPI from fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") def get_item(item_id: int): return {"id": item_id} # + validación automática, documentación automática, type hints
¿Cuándo elegir FastAPI?
- Backend API REST (microservicios)
- Servir un modelo ML
- Necesidad de rendimiento (async)
- Equipo que valora el tipado
- Documentación OpenAPI requerida
- App full-stack con plantillas HTML pesadas (Django)
- Pequeño script puntual (Flask basta)
- Equipo ya productivo con otro framework
¿Quién usa FastAPI?
El ecosistema
| Componente | Rol |
|---|---|
| Starlette | Framework ASGI subyacente |
| Pydantic | Validación y serialización |
| uvicorn | Servidor ASGI (desarrollo) |
| gunicorn | Process manager (producción) |
| SQLAlchemy | ORM (común) |
| Alembic | Migraciones de base de datos |
Resumen
- FastAPI = rendimiento + seguridad de tipos + documentación automática
- Basado en Starlette (ASGI) y Pydantic
- Ideal para APIs y microservicios modernos
- Async nativo, ~5x más rápido que Flask
Próximo paso: Parte 2 — Instalar FastAPI
Despliegue Docker y pruebas
Proyecto final • Docker • pruebas • CI/CD
Pruebas de integración clave
# tests/test_posts.py import pytest def test_create_post_requires_auth(client): r = client.post("/posts", json={"title": "Test", "content": "Hello world"}) assert r.status_code == 401 def test_create_post(client, auth_headers): r = client.post("/posts", headers=auth_headers, json={ "title": "Mon premier post", "content": "Contenu interessant ici", "tags": ["python", "fastapi"], "published": True }) assert r.status_code == 201 data = r.json() assert data["slug"] == "mon-premier-post" assert len(data["tags"]) == 2 def test_list_posts_only_published(client, auth_headers): client.post("/posts", headers=auth_headers, json={"title": "Draft", "content": "...", "published": False}) client.post("/posts", headers=auth_headers, json={"title": "Published", "content": "...", "published": True}) r = client.get("/posts") assert len(r.json()["items"]) == 1 def test_only_author_can_delete(client): h1 = make_user_and_login(client, "alice", "alice@x.com") h2 = make_user_and_login(client, "bob", "bob@x.com") r = client.post("/posts", headers=h1, json={"title": "Mien", "content": "...", "published": True}) slug = r.json()["slug"] # Bob intenta eliminar el post de Alice r = client.delete(f"/posts/{slug}", headers=h2) assert r.status_code == 403 # Alice puede r = client.delete(f"/posts/{slug}", headers=h1) assert r.status_code == 204
conftest.py final
import pytest from fastapi.testclient import TestClient from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from app.main import app from app.db import Base, get_db TestEngine = create_engine("sqlite:///./test.db", connect_args={"check_same_thread": False}) TestSession = sessionmaker(bind=TestEngine) def override_db(): db = TestSession() try: yield db finally: db.close() app.dependency_overrides[get_db] = override_db @pytest.fixture(autouse=True) def reset_db(): Base.metadata.create_all(bind=TestEngine) yield Base.metadata.drop_all(bind=TestEngine) @pytest.fixture def client(): return TestClient(app) @pytest.fixture def auth_headers(client): client.post("/auth/register", json={ "email": "test@x.com", "username": "test", "password": "secret123" }) r = client.post("/auth/login", data={ "username": "test@x.com", "password": "secret123" }) return {"Authorization": f'Bearer {r.json()["access_token"]}'}
Dockerfile
FROM python:3.11-slim AS builder WORKDIR /build COPY requirements.txt . RUN pip install --user --no-cache-dir -r requirements.txt FROM python:3.11-slim RUN useradd -m app USER app WORKDIR /home/app ENV PATH=/home/app/.local/bin:$PATH COPY --from=builder /root/.local /home/app/.local COPY --chown=app:app . . EXPOSE 8000 HEALTHCHECK CMD curl -fsS http://localhost:8000/health || exit 1 ENTRYPOINT ["./entrypoint.sh"] CMD ["gunicorn", "-c", "gunicorn.conf.py", "app.main:app"]
docker-compose.yml
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://blog:pwd@db/blogdb
SECRET_KEY: change-me-in-prod
REFRESH_SECRET_KEY: change-me-too
depends_on:
db: {condition: service_healthy}
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: blog
POSTGRES_PASSWORD: pwd
POSTGRES_DB: blogdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U blog"]
interval: 5s
retries: 5
volumes:
pgdata:CI/CD GitHub Actions
# .github/workflows/ci.yml name: CI on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: {python-version: "3.11"} - run: pip install -r requirements.txt - run: pip install pytest pytest-cov httpx - run: pytest --cov=app --cov-report=xml - uses: codecov/codecov-action@v4 build: needs: test runs-on: ubuntu-latest if: github.ref == "refs/heads/main" steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v5 with: push: true tags: ghcr.io/${{ github.repository }}:latest
Ejecutar en producción
# Entorno local completo docker-compose up -d docker-compose logs -f api docker-compose exec api alembic upgrade head # Visitar # http://localhost:8000/docs # http://localhost:8000/redoc
WebSockets con FastAPI
Hello WebSocket
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI() @app.websocket("/ws") async def websocket_endpoint(ws: WebSocket): await ws.accept() try: while True: data = await ws.receive_text() await ws.send_text(f"Echo: {data}") except WebSocketDisconnect: print("Cliente desconectado")
Prueba rápida con wscat
npm install -g wscat
wscat -c ws://localhost:8000/ws
> Hello
< Echo: HelloConnection Manager
class ConnectionManager: def __init__(self): self.active: list[WebSocket] = [] async def connect(self, ws: WebSocket): await ws.accept() self.active.append(ws) def disconnect(self, ws: WebSocket): self.active.remove(ws) async def broadcast(self, message: str): for connection in self.active: try: await connection.send_text(message) except: pass manager = ConnectionManager()
Chat broadcast
@app.websocket("/chat/{username}") async def chat(ws: WebSocket, username: str): await manager.connect(ws) await manager.broadcast(f">> {username} se unió") try: while True: msg = await ws.receive_text() await manager.broadcast(f"[{username}] {msg}") except WebSocketDisconnect: manager.disconnect(ws) await manager.broadcast(f"<< {username} salió")
Enviar JSON
@app.websocket("/json") async def json_ws(ws: WebSocket): await ws.accept() try: while True: data = await ws.receive_json() await ws.send_json({"echo": data, "server_time": str(datetime.now())}) except WebSocketDisconnect: pass
Autenticación WebSocket
from fastapi import WebSocketException, status @app.websocket("/ws") async def ws(ws: WebSocket, token: str): try: payload = decode_token(token) user_id = payload["sub"] except: raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) await ws.accept() await ws.send_text(f"Conectado como {user_id}") ... # Cliente: ws://localhost:8000/ws?token=eyJ...
Este artículo cubre los extractos más útiles — el curso completo Python FastAPI (10 capítulos, 32 lecciones, ejercicios corregidos y proyecto final) te lleva hasta el final.
./acceder-au-cours-complet curso gratuito: Vibe CodingFAQ
¿Cuánto tiempo se necesita para aprender Python FastAPI?
¿Se necesitan requisitos previos?
¿Por dónde empezar de forma concreta?
📬 ¿Quieres recibir este tipo de guía cada semana? Suscríbete gratis — código real, cero palabrería.