← ← Volver a todas las notas

Guía Completa: Configurar Entorno Full Stack con Angular, Django y Tailscale

2025-11-13 · Benja

tack moderno con Angular (frontend), Django (backend) y Tailscale como reverse proxy seguro por HTTPS. Incluye variantes, CORS/CSRF, troubleshooting y checklist. CUIDADO AL USAR, REVISAR EN CASO DE PRODUCCION

Guía Completa: Configurar Entorno Full Stack con Angular, Django y Tailscale

Guía Completa: Configurar Entorno Full Stack con Angular, Django y Tailscale

Stack: Angular (frontend), Django (API) y Tailscale como reverse proxy seguro. A continuación, además de “qué” configurar, se explica el “por qué”, para que no sea magia negra.

1) Arquitectura del sistema

┌─────────────────┐    HTTPS    ┌──────────────────┐
│   Frontend      │─────────────│   Tailscale      │
│   Angular       │             │   Reverse Proxy  │
│   Puerto 4200   │◄───────────►│   dominio.ts.net │
└─────────────────┘             └──────────────────┘
                                       │ HTTPS
                                       ▼
┌─────────────────┐             ┌──────────────────┐
│   Backend       │─────────────│   API Routes     │
│   Django        │             │   /api/*         │
│   Puerto 8000   │             └──────────────────┘
└─────────────────┘
Por qué así: Tailscale emite certificados y publica https://<algo>.ts.net, evitando montar y mantener Nginx/Let’s Encrypt para desarrollo y entornos internos. Con rutas / y /api unificás origen (evitás CORS en prod) y simplificás el front.

2) Prerrequisitos

  • Node.js 18+, Angular CLI
  • Python 3.8+, Django, DRF, django-cors-headers
  • Tailscale (cuenta + cliente)

3) Backend (Django)

3.1 Dependencias

# requirements.txt
Django==4.2.26
djangorestframework==3.14.0
django-cors-headers==4.3.1
Por qué: DRF acelera el CRUD y serialización; django-cors-headers maneja preflights CORS correctamente para clientes SPA.

3.2 Configuración base en settings.py

ALLOWED_HOSTS = ["localhost", "127.0.0.1", "0.0.0.0", ".ts.net"]

INSTALLED_APPS = [
"corsheaders",
"rest_framework",
# ...
]

MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",  # Debe ir de los primeros
"django.middleware.security.SecurityMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
# ...
]

# --- CORS (solo desarrollo) ---

CORS_ALLOW_ALL_ORIGINS = True

# Producción: usar listas/regex, no wildcard:

# CORS_ALLOWED_ORIGINS = ["[https://tu-dominio.ts.net](https://tu-dominio.ts.net)"]

# CORS_ALLOWED_ORIGIN_REGEXES = [r"^[https://.*.ts.net$](https://.*.ts.net$)"]

CORS_ALLOW_METHODS = ["GET","POST","PUT","PATCH","DELETE","OPTIONS"]
CORS_ALLOW_HEADERS = [
"accept","accept-encoding","authorization","content-type","dnt",
"origin","user-agent","x-csrftoken","x-requested-with"
]
CORS_ALLOW_CREDENTIALS = True  # si usás cookies/sesiones

# --- CSRF (requerido si usás cookies) ---

CSRF_TRUSTED_ORIGINS = ["https://*.ts.net"]
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True

# --- Reverse proxy/HTTPS ---

USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
  • ALLOWED_HOSTS=".ts.net": Django no servirá peticiones si el host no está permitido; esencial tras el proxy.
  • CorsMiddleware primero: para que capte las peticiones OPTIONS (preflight) antes que otros middlewares.
  • CORS en dev vs prod: ALLOW_ALL acelera dev; en prod restringí orígenes para mitigar abuso desde sitios externos.
  • CSRF_TRUSTED_ORIGINS: con HTTPS tras proxy, Django valida el origen; sin esto, verás 403 CSRF.
  • SECURE_PROXY_SSL_HEADER / USE_X_FORWARDED_HOST: Django entiende que “delante” hay TLS y arma URLs correctas; evita redirecciones erróneas y cookies “no seguras”.

3.3 URLs DRF

# server/urls.py
from django.urls import path, include
from rest_framework import routers
from app.views import ClienteViewSet

router = routers.DefaultRouter()
router.register(r'clientes', ClienteViewSet, basename='cliente')

urlpatterns = [
path("api/", include(router.urls)),
]
Por qué: versionar y agrupar bajo /api/ permite que el reverse proxy de Tailscale enrute limpio con --set-path=/api y deja espacio para servir el frontend en /.

4) Frontend (Angular)

4.1 Variables de entorno

// environment.ts
export const environment = {
  production: false,
  apiUrl: 'https://TU_DOMINIO.ts.net/api'
};

// environment.prod.ts
export const environment = {
production: true,
apiUrl: '[https://TU_DOMINIO.ts.net/api](https://TU_DOMINIO.ts.net/api)'
};
Por qué: el origen de API cambia por entorno; centralizar la base URL evita “string hunting” y reduce errores de Mixed Content.

4.2 Interceptor (base URL + credenciales)

// api.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
import { environment } from '../../environments/environment';

@Injectable()
export class ApiInterceptor implements HttpInterceptor {
intercept(req: HttpRequest, next: HttpHandler) {
const isApi = req.url.startsWith('/api/');
const url = isApi ? `${environment.apiUrl}${req.url.replace('/api', '')}` : req.url;
return next.handle(req.clone({ url, withCredentials: true }));
}
}
Por qué: withCredentials es necesario si usás cookies/sesiones en Django; sin esto, el navegador no envía/recibe cookies y falla la auth.

4.3 Servicio de ejemplo

// clientes.service.ts
listar() { return this.http.get<any[]>('/api/clientes/'); }
crear(payload: any) { return this.http.post('/api/clientes/', payload); }

4.4 Dev sin CORS con proxy local

{
  "/api": {
    "target": "http://localhost:8000",
    "secure": false,
    "changeOrigin": true,
    "logLevel": "debug"
  }
}
ng serve --proxy-config proxy.conf.json
Por qué: servir Angular y Django desde localhost distintos origina CORS. El proxy de Angular hace de “puente” y te permite pedir /api al mismo origen del Front en dev, sin tocar CORS del backend.

5) Tailscale

tailscale up
tailscale status

# Publicar servicios bajo [https://TU_DOMINIO.ts.net](https://TU_DOMINIO.ts.net)

tailscale serve --bg --set-path=/api 8000   # Django
tailscale serve --bg --set-path=/ 4200      # Angular
tailscale serve status
Por qué: tailscale serve oficia de reverse proxy TLS (certs automáticos), mapea rutas a puertos locales y te da un único dominio seguro para el navegador.

6) Integración, CORS y CSRF

  • Dev: CORS_ALLOW_ALL_ORIGINS=True para rapidez o usá proxy Angular (mejor).
  • Prod: restringí con CORS_ALLOWED_ORIGINS o regex y habilitá CORS_ALLOW_CREDENTIALS si usás cookies.
  • CSRF: agregá CSRF_TRUSTED_ORIGINS a https://*.ts.net; sin eso, el middleware bloqueará POST/PUT/PATCH/DELETE con 403.

7) Casos de configuración

  1. Caso A (Dev rápido): Angular + proxy.conf.json, Django en localhost:8000. Cero CORS.
  2. Caso B (Un dominio): tailscale serve con / → 4200 y /api → 8000. Ideal para demos internas.
  3. Caso C (Hosts separados): frontend.ts.net y backend.ts.net; o proxiar /api en el host del front para mantener un solo origen.
  4. Caso D (Prod estático): construir Angular y servir assets con Django + WhiteNoise.
    INSTALLED_APPS = ["whitenoise.runserver_nostatic","django.contrib.staticfiles", ...]
    MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")
    STATIC_URL = "/static/"
    STATIC_ROOT = BASE_DIR / "staticfiles"
    Por qué: WhiteNoise sirve estáticos eficientemente sin Nginx para entornos simples; cache y compresión incluidos.
  5. Caso E (Autenticación):
    • JWT en header: simple en CORS; cuidado dónde guardás el token.
    • Cookies HttpOnly + CSRF: más seguro contra XSS; requiere withCredentials y configuración CSRF correcta.

8) Solución de problemas

  • CORS bloqueado: en dev usar ALLOW_ALL o proxy; en prod declarar orígenes explícitos.
  • 403 CSRF: agregar CSRF_TRUSTED_ORIGINS, mandar cookie y/o header X-CSRFToken, y withCredentials del lado de Angular.
  • Mixed Content: siempre https:// hacia *.ts.net.
  • SSL/puertos solapados:
    tailscale serve reset
    tailscale serve drain svc:8000
    tailscale serve drain svc:4200

9) Comandos de referencia

# Tailscale
tailscale status
tailscale serve status
tailscale serve --bg --set-path=/ruta [puerto]
tailscale serve drain svc:[puerto]
tailscale serve reset

# Django

pip install -r requirements.txt
python manage.py migrate
python manage.py runserver 0.0.0.0:8000

# Angular

npm i
ng serve --host 0.0.0.0 --port 4200
ng serve --proxy-config proxy.conf.json
ng build --configuration production

# Verificación

curl -i [https://TU_DOMINIO.ts.net/api/clientes/](https://TU_DOMINIO.ts.net/api/clientes/)
curl -I [https://TU_DOMINIO.ts.net/](https://TU_DOMINIO.ts.net/)
tailscale ping TU_DOMINIO.ts.net

10) Checklist rápido

  • CSRF_TRUSTED_ORIGINS incluye https://*.ts.net
  • En prod: CORS_ALLOW_ALL_ORIGINS = False y orígenes definidos
  • Angular usa /api/* y, si hay cookies, withCredentials
  • tailscale serve status muestra / → 4200 y /api → 8000
  • curl a /api/ devuelve 200/401 esperado
Reemplazá TU_DOMINIO por tu subdominio real de Tailscale en todas las configuraciones.