Manual Completo de Construcción y Despliegue

Guía extensa para implementar proyectos FastAPI, Django con React/Next.js y despliegue en múltiples plataformas

Introducción

Este manual cubre el proceso completo desde la creación inicial de proyectos backend con FastAPI y Django, integración con frontends modernos (React y Next.js), hasta el despliegue automatizado en diversas plataformas utilizando Docker y CI/CD.

Nota: Este documento asume conocimientos básicos de desarrollo web, Docker y sistemas de control de versiones. Cada sección incluye ejemplos prácticos y configuraciones detalladas.

Estructura de Proyecto

La arquitectura recomendada para proyectos modernos sigue el patrón de microservicios o monolito modular, dependiendo de los requisitos.

Estructura para FastAPI

fastapi-project/
├── app/
│   ├── __init__.py
│   ├── main.py          # Punto de entrada principal
│   ├── api/             # Routers y endpoints
│   ├── models/          # Modelos Pydantic/SQLAlchemy
│   ├── schemas/         # Esquemas Pydantic
│   ├── db/              # Configuración de base de datos
│   ├── utils/           # Utilidades comunes
│   └── tests/           # Pruebas
├── requirements.txt     # Dependencias Python
├── Dockerfile
├── docker-compose.yml
├── .env                 # Variables de entorno
└── README.md

Estructura para Django

django-project/
├── project/             # Configuración principal
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── asgi.py/wsgi.py
├── apps/
│   └── core/           # Aplicación de ejemplo
│       ├── migrations/
│       ├── __init__.py
│       ├── models.py
│       ├── views.py
│       └── serializers.py
├── manage.py
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
└── README.md

Estructura para Frontend (React/Next.js)

frontend/
├── public/              # Assets estáticos
├── src/
│   ├── components/      # Componentes reutilizables
│   ├── pages/           # Páginas (Next.js) o rutas
│   ├── styles/          # Estilos globales
│   ├── utils/           # Funciones utilitarias
│   ├── contexts/        # Contextos de React
│   ├── hooks/           # Hooks personalizados
│   └── api/             # Llamadas a la API backend
├── next.config.js       # Config Next.js
├── package.json
├── Dockerfile
└── README.md

Configuración Inicial de Proyectos

1. FastAPI Básico

Crea un entorno virtual e instala FastAPI:

# Crear proyecto
mkdir fastapi-project && cd fastapi-project
python -m venv venv
source venv/bin/activate  # Linux/Mac
venv\Scripts\activate    # Windows

# Instalar dependencias
pip install fastapi uvicorn sqlalchemy psycopg2-binary python-dotenv

# Crear estructura básica
mkdir app && touch app/main.py

Contenido básico de app/main.py:

from fastapi import FastAPI
from dotenv import load_dotenv

load_dotenv()

app = FastAPI(title="Mi API FastAPI")

@app.get("/")
def read_root():
    return {"message": "Bienvenido a mi API"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

2. Django Básico

# Crear proyecto
mkdir django-project && cd django-project
python -m venv venv
source venv/bin/activate  # Linux/Mac
venv\Scripts\activate    # Windows

# Instalar Django
pip install django psycopg2-binary python-dotenv

# Crear proyecto y app
django-admin startproject project .
python manage.py startapp core

Configuración básica en project/settings.py:

# settings.py
from dotenv import load_dotenv
import os

load_dotenv()

# Configuración de base de datos
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('DB_NAME'),
        'USER': os.getenv('DB_USER'),
        'PASSWORD': os.getenv('DB_PASSWORD'),
        'HOST': os.getenv('DB_HOST'),
        'PORT': os.getenv('DB_PORT'),
    }
}

# Configuración de CORS (si se usa con frontend)
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]

3. Frontend con Next.js

# Crear proyecto Next.js
npx create-next-app@latest frontend --typescript
cd frontend

# Instalar dependencias comunes
npm install axios react-query @tanstack/react-query @mui/material @emotion/react @emotion/styled

Configuración básica para conectar con el backend en src/utils/api.ts:

import axios from 'axios';

const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
  headers: {
    'Content-Type': 'application/json',
  },
});

// Interceptor para manejar errores
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response) {
      console.error('Error de API:', error.response.data);
    } else {
      console.error('Error de conexión:', error.message);
    }
    return Promise.reject(error);
  }
);

export default api;

Dockerización de Aplicaciones

La containerización con Docker permite empaquetar aplicaciones con todas sus dependencias para un despliegue consistente.

1. Dockerfile para FastAPI

# Usar imagen oficial de Python
FROM python:3.9-slim

# Establecer directorio de trabajo
WORKDIR /app

# Copiar requirements primero para cachear
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar el resto de la aplicación
COPY . .

# Variables de entorno
ENV PYTHONPATH=/app
ENV PORT=8000

# Exponer puerto
EXPOSE $PORT

# Comando para ejecutar la aplicación
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

2. Dockerfile para Django

# Usar imagen oficial de Python
FROM python:3.9-slim

# Establecer variables de entorno
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Establecer directorio de trabajo
WORKDIR /app

# Instalar dependencias del sistema
RUN apt-get update && apt-get install -y \
    gcc \
    python3-dev \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Instalar dependencias de Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copiar proyecto
COPY . .

# Comando para ejecutar la aplicación
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "project.wsgi:application"]

3. Dockerfile para Next.js

# Fase de construcción
FROM node:16-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Fase de producción
FROM node:16-alpine

WORKDIR /app
ENV NODE_ENV production

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

EXPOSE 3000
CMD ["npm", "start"]

4. docker-compose.yml para Desarrollo

Archivo completo para desarrollo con backend, frontend y base de datos:

version: '3.8'

services:
  db:
    image: postgres:13
    environment:
      POSTGRES_USER: ${DB_USER:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
      POSTGRES_DB: ${DB_NAME:-appdb}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  backend:
    build:
      context: .
      dockerfile: Dockerfile
    command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
    volumes:
      - .:/app
    environment:
      - DB_HOST=db
      - DB_USER=${DB_USER:-postgres}
      - DB_PASSWORD=${DB_PASSWORD:-postgres}
      - DB_NAME=${DB_NAME:-appdb}
      - DB_PORT=5432
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    command: npm run dev
    volumes:
      - ./frontend:/app
      - /app/node_modules
    environment:
      - NEXT_PUBLIC_API_URL=http://backend:8000
    ports:
      - "3000:3000"
    depends_on:
      - backend

volumes:
  postgres_data:

Nota: Para producción, se recomienda configurar volúmenes separados para datos persistentes, optimizar las imágenes y configurar recursos límite.

Configuración de Bases de Datos

1. PostgreSQL con FastAPI

Configuración con SQLAlchemy y Alembic para migraciones:

# app/db/base.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "postgresql://user:password@db:5432/dbname"

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()

Modelo de ejemplo:

# app/models/item.py
from sqlalchemy import Column, Integer, String
from app.db.base import Base

class Item(Base):
    __tablename__ = "items"
    
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    description = Column(String, index=True)

2. PostgreSQL con Django

Configuración en settings.py:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('DB_NAME', 'appdb'),
        'USER': os.getenv('DB_USER', 'postgres'),
        'PASSWORD': os.getenv('DB_PASSWORD', 'postgres'),
        'HOST': os.getenv('DB_HOST', 'db'),
        'PORT': os.getenv('DB_PORT', '5432'),
    }
}

Modelo de ejemplo:

# apps/core/models.py
from django.db import models

class Item(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    
    def __str__(self):
        return self.title

3. MongoDB (Opcional)

Configuración con Motor (async) para FastAPI:

# app/db/mongodb.py
from motor.motor_asyncio import AsyncIOMotorClient
from dotenv import load_dotenv
import os

load_dotenv()

MONGO_DETAILS = os.getenv("MONGO_URI")

client = AsyncIOMotorClient(MONGO_DETAILS)
database = client.get_database("appdb")

# Colecciones
items_collection = database.get_collection("items")

Integración con Frontend

1. React Query para Gestión de Estado

Configuración en src/_app.tsx (Next.js):

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutos
      cacheTime: 15 * 60 * 1000, // 15 minutos
      retry: 1,
    },
  },
})

function MyApp({ Component, pageProps }) {
  return (
    <QueryClientProvider client={queryClient}>
      <Component {...pageProps} />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  )
}

Hook personalizado para llamadas API:

// src/hooks/useItems.ts
import { useQuery } from '@tanstack/react-query'
import api from '../utils/api'

const fetchItems = async () => {
  const { data } = await api.get('/items')
  return data
}

export const useItems = () => {
  return useQuery(['items'], fetchItems)
}

2. Autenticación JWT

Backend (FastAPI):

# app/api/auth.py
from fastapi import APIRouter, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional

router = APIRouter()

# Configuración
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# Funciones de autenticación
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

@router.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # Verificar usuario y contraseña (implementar según tu DB)
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=400, 
            detail="Usuario o contraseña incorrectos"
        )
    
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, 
        expires_delta=access_token_expires
    )
    
    return {"access_token": access_token, "token_type": "bearer"}

Frontend (React/Next.js):

// src/context/AuthContext.tsx
import { createContext, useContext, ReactNode, useState, useEffect } from 'react'
import api from '../utils/api'
import { useRouter } from 'next/router'

type AuthContextType = {
  user: any
  login: (email: string, password: string) => Promise<void>
  logout: () => void
  isAuthenticated: boolean
}

const AuthContext = createContext<AuthContextType>({} as AuthContextType)

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<any>(null)
  const router = useRouter()

  useEffect(() => {
    async function loadUser() {
      const token = localStorage.getItem('token')
      if (token) {
        try {
          api.defaults.headers.common['Authorization'] = `Bearer ${token}`
          const { data } = await api.get('/users/me')
          setUser(data)
        } catch (err) {
          logout()
        }
      }
    }
    loadUser()
  }, [])

  const login = async (email: string, password: string) => {
    const { data } = await api.post('/token', {
      username: email,
      password,
    })
    localStorage.setItem('token', data.access_token)
    api.defaults.headers.common['Authorization'] = `Bearer ${data.access_token}`
    const userResponse = await api.get('/users/me')
    setUser(userResponse.data)
    router.push('/dashboard')
  }

  const logout = () => {
    localStorage.removeItem('token')
    delete api.defaults.headers.common['Authorization']
    setUser(null)
    router.push('/login')
  }

  return (
    <AuthContext.Provider
      value={{
        user,
        login,
        logout,
        isAuthenticated: !!user,
      }}
    >
      {children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)

Pruebas Automatizadas

1. Pruebas en FastAPI

Configuración básica con pytest:

# tests/test_main.py
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_read_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Bienvenido a mi API"}

def test_read_item():
    response = client.get("/items/42?q=test")
    assert response.status_code == 200
    assert response.json() == {"item_id": 42, "q": "test"}

2. Pruebas en Django

# core/tests/test_models.py
from django.test import TestCase
from core.models import Item

class ItemModelTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        Item.objects.create(title='Test item', description='Test description')

    def test_title_content(self):
        item = Item.objects.get(id=1)
        expected_object_name = f'{item.title}'
        self.assertEqual(expected_object_name, 'Test item')

3. Pruebas en React con Jest

// src/components/__tests__/ItemList.test.tsx
import { render, screen } from '@testing-library/react'
import ItemList from '../ItemList'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient()

describe('ItemList', () => {
  it('renders loading state', () => {
    render(
      <QueryClientProvider client={queryClient}>
        <ItemList />
      </QueryClientProvider>
    )
    expect(screen.getByText('Loading...')).toBeInTheDocument()
  })
})

Configuración de CI/CD

1. GitHub Actions para FastAPI

# .github/workflows/main.yml
name: FastAPI CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest pytest-cov
    
    - name: Run tests
      env:
        DB_HOST: localhost
        DB_USER: postgres
        DB_PASSWORD: postgres
        DB_NAME: testdb
      run: |
        pytest --cov=app --cov-report=xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v1
    
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Login to Docker Hub
      uses: docker/login-action@v1
      with:
        username: ${{ secrets.DOCKER_HUB_USERNAME }}
        password: ${{ secrets.DOCKER_HUB_TOKEN }}
    
    - name: Build and push
      uses: docker/build-push-action@v2
      with:
        context: .
        push: true
        tags: ${{ secrets.DOCKER_HUB_USERNAME }}/fastapi-app:latest

2. GitLab CI/CD para Django

# .gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: ""

services:
  - docker:dind

test:
  stage: test
  image: python:3.9
  services:
    - postgres:13
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: postgres
    POSTGRES_PASSWORD: postgres
    DB_HOST: postgres
  before_script:
    - pip install -r requirements.txt
    - pip install pytest pytest-cov
  script:
    - pytest --cov=project --cov-report=xml
  artifacts:
    reports:
      cobertura: coverage.xml

build:
  stage: build
  image: docker:latest
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:latest

deploy:
  stage: deploy
  image: alpine:latest
  needs: ["build"]
  script:
    - apk add --no-cache openssh-client rsync
    - echo "$SSH_PRIVATE_KEY" > ssh_key
    - chmod 600 ssh_key
    - ssh -i ssh_key -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "docker pull $CI_REGISTRY_IMAGE:latest && docker-compose up -d"

3. Despliegue Automático para Next.js

# .github/workflows/nextjs.yml
name: Next.js CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v2
      
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test
  
  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v2
      
      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          scope: ${{ secrets.VERCEL_ORG_ID }}

Opciones de Despliegue

1. Render

Configuración para desplegar en Render:

  • Crea un nuevo servicio Web en Render
  • Conecta tu repositorio GitHub/GitLab
  • Configura las variables de entorno
  • Especifica el comando de inicio (ej: gunicorn --bind 0.0.0.0:$PORT project.wsgi)

Ejemplo de render.yaml:

services:
  - type: web
    name: django-app
    env: python
    buildCommand: pip install -r requirements.txt
    startCommand: gunicorn --bind 0.0.0.0:$PORT project.wsgi
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: appdb
          property: connectionString

2. Vercel

Para frontends Next.js:

  • Importa tu proyecto desde GitHub
  • Configura como framework Next.js
  • Agrega variables de entorno
  • Configura dominios personalizados si es necesario

Para backends (usando funciones serverless):

// api/hello.js
module.exports = (req, res) => {
  res.json({ message: 'Hello from Vercel!' })
}

3. fly.io

Configuración con Docker:

  1. Instala flyctl y haz login
  2. Ejecuta flyctl launch
  3. Configura tu fly.toml:
# fly.toml
app = "your-app-name"

[build]
  image = "your-docker-image"

[env]
  PORT = "8000"

[[services]]
  internal_port = 8000
  protocol = "tcp"
  
  [services.concurrency]
    hard_limit = 25
    soft_limit = 20
  
  [[services.ports]]
    handlers = ["http"]
    port = "80"
  
  [[services.ports]]
    handlers = ["tls", "http"]
    port = "443"

4. AWS (ECS/EKS)

Despliegue con ECS:

  1. Crea un repositorio ECR
  2. Sube tu imagen Docker
  3. Crea un cluster ECS
  4. Define un task definition
  5. Configura un servicio

Ejemplo de task definition:

{
  "family": "fastapi-app",
  "networkMode": "awsvpc",
  "executionRoleArn": "arn:aws:iam::account-id:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "fastapi-container",
      "image": "account-id.dkr.ecr.region.amazonaws.com/fastapi-app:latest",
      "portMappings": [
        {
          "containerPort": 8000,
          "hostPort": 8000,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "environment": [
        {"name": "DB_HOST", "value": "your-rds-endpoint"},
        {"name": "DB_NAME", "value": "appdb"}
      ],
      "secrets": [
        {"name": "DB_PASSWORD", "valueFrom": "arn:aws:ssm:region:account-id:parameter/db-password"}
      ]
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "1024",
  "memory": "2048"
}

5. VPS Tradicional

Configuración manual con Docker:

  1. Configura tu servidor (Ubuntu/Debian)
  2. Instala Docker y Docker Compose
  3. Copia tus archivos (Dockerfile, docker-compose.yml)
  4. Configura un proxy inverso (Nginx)
  5. Configura SSL con Let's Encrypt

Ejemplo de configuración Nginx:

server {
    listen 80;
    server_name yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain.com;
    
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    
    location / {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    location /static/ {
        alias /path/to/your/static/files;
    }
}

6. Serverless (AWS Lambda)

Configuración con Serverless Framework:

# serverless.yml
service: fastapi-serverless

provider:
  name: aws
  runtime: python3.9
  region: us-east-1
  memorySize: 512
  timeout: 30

functions:
  app:
    handler: wsgi_handler.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'

plugins:
  - serverless-python-requirements
  - serverless-wsgi

custom:
  wsgi:
    app: app.main.app
    packRequirements: false
  pythonRequirements:
    dockerizePip: true

Adaptador WSGI para Lambda:

# wsgi_handler.py
from mangum import Mangum
from app.main import app

handler = Mangum(app)

Monitoreo y Logging

1. FastAPI - Middleware de Logging

# app/middleware/logging.py
import logging
import time
from fastapi import Request

logger = logging.getLogger(__name__)

async def log_requests(request: Request, call_next):
    start_time = time.time()
    
    response = await call_next(request)
    
    process_time = (time.time() - start_time) * 1000
    formatted_time = "{0:.2f}".format(process_time)
    
    logger.info(
        f"method={request.method} path={request.url.path} "
        f"status_code={response.status_code} "
        f"processed_in={formatted_time}ms"
    )
    
    return response

2. Django - Sentry

# settings.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn="your-sentry-dsn",
    integrations=[DjangoIntegration()],
    traces_sample_rate=1.0,
    send_default_pii=True
)

3. Frontend - LogRocket

// src/lib/logrocket.js
import LogRocket from 'logrocket'
import setupLogRocketReact from 'logrocket-react'

if (process.env.NODE_ENV === 'production') {
  LogRocket.init('your-app-id')
  setupLogRocketReact(LogRocket)
}

export default LogRocket

4. Prometheus y Grafana

Configuración para FastAPI:

# app/monitoring/prometheus.py
from prometheus_client import make_asgi_app, Counter, Histogram

REQUEST_COUNT = Counter(
    'request_count', 'App Request Count',
    ['method', 'endpoint', 'http_status']
)

REQUEST_LATENCY = Histogram(
    'request_latency_seconds', 'Request latency',
    ['method', 'endpoint']
)

def monitor_request(request, response):
    REQUEST_COUNT.labels(
        request.method, request.url.path, response.status_code
    ).inc()

def record_latency(request, latency):
    REQUEST_LATENCY.labels(
        request.method, request.url.path
    ).observe(latency)

Configuración de docker-compose para monitoreo:

services:
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
  
  grafana:
    image: grafana/grafana
    ports:
      - "3001:3000"
    volumes:
      - grafana-storage:/var/lib/grafana
    depends_on:
      - prometheus

volumes:
  grafana-storage:

Mejores Prácticas de Seguridad

1. Configuración de Docker

  • No ejecutar contenedores como root
  • Usar imágenes oficiales y mantenerlas actualizadas
  • Limitar recursos (CPU, memoria)
  • Usar volúmenes para datos persistentes
  • Configurar healthchecks
# Ejemplo de Dockerfile seguro
FROM python:3.9-slim

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app
COPY --chown=appuser:appgroup . .

USER appuser

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "project.wsgi"]

2. Configuración Django

  • Configuración para producción:
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_SECONDS = 31536000  # 1 año
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

3. Configuración FastAPI

  • Middlewares de seguridad:
from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware

app = FastAPI()

app.add_middleware(HTTPSRedirectMiddleware)
app.add_middleware(
    TrustedHostMiddleware,
    allowed_hosts=["example.com", "*.example.com"]
)

4. Frontend Seguro

  • Configurar CSP (Content Security Policy)
  • Protección contra XSS
  • Validar inputs del usuario
  • Usar HTTPS para todas las conexiones
  • Gestión segura de tokens JWT
// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY'
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block'
          }
        ]
      }
    ]
  }
}

Optimización de Rendimiento

1. Backend

  • Cache con Redis
  • Paginación de resultados
  • Consultas optimizadas a la base de datos
  • Compresión de respuestas
# FastAPI con cache
from fastapi import FastAPI
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from redis import asyncio as aioredis

app = FastAPI()

@app.on_event("startup")
async def startup():
    redis = aioredis.from_url("redis://redis")
    FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")

2. Frontend

  • Code splitting
  • Lazy loading de componentes
  • Optimización de imágenes
  • Server-side rendering (Next.js)
// Next.js dynamic import
import dynamic from 'next/dynamic'

const HeavyComponent = dynamic(
  () => import('../components/HeavyComponent'),
  { loading: () => <p>Loading...</p>, ssr: false }
)

3. Base de Datos

  • Índices adecuados
  • Particionamiento de tablas
  • Replicas de lectura
  • Connection pooling
# Django connection pooling
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'USER': 'user',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '5432',
        'OPTIONS': {
            'MAX_CONNS': 20,
        }
    }
}

4. CDN y Caching

  • Configurar cabeceras Cache-Control
  • Usar CDN para assets estáticos
  • Implementar service workers
  • Precargar recursos críticos
// next.config.js - Headers de cache
module.exports = {
  async headers() {
    return [
      {
        source: '/_next/static/(.*)',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
    ]
  },
}

Conclusión

Este manual ha cubierto el ciclo completo de desarrollo y despliegue de aplicaciones web modernas, desde la configuración inicial hasta el monitoreo en producción. Las tecnologías presentadas (FastAPI, Django, React, Next.js) combinadas con las estrategias de despliegue (Docker, CI/CD, múltiples plataformas) proporcionan una base sólida para construir aplicaciones escalables y mantenibles.