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.

Lánzate a Python FastAPI: tu primer paso concreto hoy

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.

tl;dr
  • Introducción e instalación
  • Rutas y métodos
  • Pydantic y validación
  • Inyección de dependencias
  • Base de datos
~$ cat ./parcours.md # Python FastAPI — 10 capítulos
01
Introducción e instalación
→ ¿Por qué FastAPI?→ Instalar FastAPI y uvicorn+ 1 más lecciones
02
Rutas y métodos
→ Path params y query params→ POST, PUT, PATCH, DELETE+ 1 más lecciones
03
Pydantic y validación
→ BaseModel para las solicitudes→ Validators y constraints+ 2 más lecciones
04
Inyección de dependencias
→ Depends - el principio→ Dependencias con yield+ 1 más lecciones
05
Base de datos
→ SQLAlchemy con FastAPI→ CRUD completo+ 1 más lecciones
06
Autenticación JWT
→ OAuth2 + JWT→ Roles y permisos+ 1 más lecciones
07
Async y rendimiento
→ async vs sync→ BackgroundTasks+ 1 más lecciones
08
Pruebas
→ TestClient y pytest→ Override dependencies+ 1 más lecciones
🏁
Proyecto final (+ 2 capítulos en camino)
→ Te vas con un proyecto concreto y demostrable

¿Por qué FastAPI?

NOTEObjetivo — Comprender qué distingue a FastAPI de otros frameworks Python y cuándo elegirlo.

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

PoderDetalle
RendimientoAsync nativo vía ASGI/uvicorn, comparable a Node/Go
Seguridad de tiposType hints de Python -> validación en runtime vía Pydantic
Documentación automáticaSwagger UI + ReDoc generados automáticamente
Experiencia de desarrolloAutocompletado en IDE, errores claros, dependencias inyectables

Comparación Flask vs FastAPI vs Django

CriterioFlaskFastAPIDjango
EstiloMicroMicro (enfocado en API)Full-stack
Async nativoNo (extensiones)Parcial (DRF no)
Type hintsNoSÍ (Pydantic)No
Documentación automáticaNoSÍ (Swagger)Vía DRF (manual)
ORM incluidoNoNoSÍ (Django ORM)
AprendizajeMuy fácilFácilModerado
Rendimiento (req/s)~5k~20-30k~3k

Pequeño ejemplo: "Hello World"

output
# Flask
from flask import Flask
app = Flask(__name__)

@app.route("/items/<int:item_id>")
def get_item(item_id):
    return {"id": item_id}
output
# 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?

TIP
  • Backend API REST (microservicios)
  • Servir un modelo ML
  • Necesidad de rendimiento (async)
  • Equipo que valora el tipado
  • Documentación OpenAPI requerida
WARNINGNO
  • 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

ComponenteRol
StarletteFramework ASGI subyacente
PydanticValidación y serialización
uvicornServidor ASGI (desarrollo)
gunicornProcess manager (producción)
SQLAlchemyORM (común)
AlembicMigraciones de base de datos

Resumen

NOTEPara recordar
  • 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

NOTEObjetivo — Finalizar el proyecto: pruebas con pytest, Dockerfile, docker-compose, CI con GitHub Actions.

Pruebas de integración clave

output
# 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

output
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

output
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

output
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

output
# .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

output
# 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

NOTEObjetivo — Construir un chat en tiempo real con WebSocket nativo de FastAPI.

Hello WebSocket

output
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

output
npm install -g wscat
wscat -c ws://localhost:8000/ws
> Hello
< Echo: Hello

Connection Manager

output
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

output
@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

output
@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

output
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...
va-plus-loin

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 Coding

FAQ

¿Cuánto tiempo se necesita para aprender Python FastAPI?
Con una progresión estructurada (10 capítulos, 32 lecciones cortas y prácticas), se alcanza un nivel operativo en unas semanas dedicando 30 a 60 minutos al día. Lo importante es practicar cada concepto de inmediato.
¿Se necesitan requisitos previos?
Con nociones básicas de informática basta. Si sabes usar una terminal y leer código sencillo, estás listo.
¿Por dónde empezar de forma concreta?
Reproduce los comandos de este artículo y luego sigue el curso completo Python FastAPI: encadena las 32 lecciones en orden, con ejercicios y proyecto final.

📬 ¿Quieres recibir este tipo de guía cada semana? Suscríbete gratis — código real, cero palabrería.