Configuración Avanzada de Directus Headless CMS
Directus es un headless CMS y plataforma de datos de código abierto que convierte cualquier base de datos SQL en una API REST y GraphQL con panel de administración. Sus características avanzadas como Flows para automatización, permisos granulares por campo, extensiones personalizadas y soporte para múltiples bases de datos lo convierten en una solución enterprise accesible. Esta guía cubre las configuraciones avanzadas más importantes de Directus en entornos de producción Linux.
Requisitos Previos
- Docker y Docker Compose instalados
- PostgreSQL, MySQL o SQLite (Directus soporta todos)
- 1 GB RAM mínimo (recomendado 2 GB)
- Node.js 18+ (solo para desarrollo de extensiones)
Instalación con Docker
# Crear estructura de directorios
mkdir -p /opt/directus/{uploads,extensions,database}
cd /opt/directus
# docker-compose.yml para Directus con PostgreSQL
cat > docker-compose.yml << 'EOF'
version: '3'
services:
database:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: directus
POSTGRES_USER: directus
POSTGRES_PASSWORD: password_seguro_aqui
restart: unless-stopped
cache:
image: redis:7-alpine
restart: unless-stopped
directus:
image: directus/directus:latest
ports:
- "8055:8055"
volumes:
- ./uploads:/directus/uploads
- ./extensions:/directus/extensions
depends_on:
- database
- cache
environment:
SECRET: secreto-muy-largo-y-aleatorio-de-produccion
# Base de datos
DB_CLIENT: pg
DB_HOST: database
DB_PORT: 5432
DB_DATABASE: directus
DB_USER: directus
DB_PASSWORD: password_seguro_aqui
# Caché con Redis
CACHE_ENABLED: "true"
CACHE_STORE: redis
REDIS: redis://cache:6379
# Admin inicial
ADMIN_EMAIL: [email protected]
ADMIN_PASSWORD: AdminPassword123!
# URL pública
PUBLIC_URL: https://cms.tudominio.com
# CORS
CORS_ENABLED: "true"
CORS_ORIGIN: "https://tuapp.com"
# Email
EMAIL_FROM: [email protected]
EMAIL_TRANSPORT: smtp
EMAIL_SMTP_HOST: smtp.tudominio.com
EMAIL_SMTP_PORT: 587
EMAIL_SMTP_USER: [email protected]
EMAIL_SMTP_PASSWORD: tu-smtp-password
# Storage
STORAGE_LOCATIONS: local
STORAGE_LOCAL_DRIVER: local
STORAGE_LOCAL_ROOT: ./uploads
restart: unless-stopped
volumes:
postgres_data:
EOF
# Iniciar Directus
docker compose up -d
# Ver logs del inicio
docker compose logs directus -f
El panel de administración estará disponible en http://localhost:8055.
Sistema de Permisos Granulares
Directus tiene un sistema de permisos por rol, colección y campo:
Configurar roles y permisos vía API
# Obtener token de admin
TOKEN=$(curl -s -X POST http://localhost:8055/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"AdminPassword123!"}' \
| jq -r '.data.access_token')
# Crear un nuevo rol "Editor"
ROL_ID=$(curl -s -X POST http://localhost:8055/roles \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Editor",
"description": "Puede editar contenido pero no configuración",
"admin_access": false,
"app_access": true
}' | jq -r '.data.id')
echo "Rol Editor creado con ID: $ROL_ID"
# Asignar permisos a la colección "articulos" para el rol Editor
curl -X POST http://localhost:8055/permissions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"role\": \"$ROL_ID\",
\"collection\": \"articulos\",
\"action\": \"read\",
\"fields\": \"*\",
\"filter\": {\"estado\": {\"_eq\": \"publicado\"}}
}"
# Permiso de actualización solo en sus propios artículos
curl -X POST http://localhost:8055/permissions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"role\": \"$ROL_ID\",
\"collection\": \"articulos\",
\"action\": \"update\",
\"fields\": [\"titulo\", \"contenido\", \"estado\"],
\"filter\": {\"autor\": {\"_eq\": \"\$CURRENT_USER\"}}
}"
Permisos por campo (Field-level Security)
# Limitar qué campos puede ver un rol en la colección "usuarios"
curl -X POST http://localhost:8055/permissions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"role\": \"$ROL_ID\",
\"collection\": \"directus_users\",
\"action\": \"read\",
\"fields\": [\"id\", \"first_name\", \"last_name\", \"email\"],
\"comment\": \"Los editores no pueden ver campos sensibles\"
}"
Flows: Automatización Sin Código
Los Flows de Directus permiten crear automatizaciones visuales que responden a eventos o se ejecutan en horario:
Crear un Flow por API
# Crear un Flow que envía webhook al publicar un artículo
curl -X POST http://localhost:8055/flows \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Notificar publicación",
"status": "active",
"trigger": "event",
"options": {
"type": "filter",
"scope": ["items.update"],
"collections": ["articulos"]
}
}'
Tipos de triggers en Flows
- Event Hook: Responde a operaciones en colecciones (crear, actualizar, eliminar)
- Webhook Trigger: Expone un endpoint HTTP para disparar el flow
- Schedule: Ejecuta el flow en horario cron
- Manual: Se activa desde el panel de administración
Operaciones disponibles en Flows
Desde el panel visual de Flows (Settings > Flows), puedes encadenar:
- Condition: Lógica condicional (if/else)
- Run Script: Código JavaScript personalizado
- Request URL: Llamada HTTP a API externa
- Send Email: Enviar email con plantilla
- Send Notification: Notificación en el panel
- Create/Read/Update/Delete Item: CRUD en colecciones
- Transform Payload: Transformar datos con JavaScript
// Ejemplo de operación "Run Script" en un Flow
// Código para calcular el tiempo de lectura de un artículo
const { contenido } = $input.body;
const palabras = contenido.split(' ').length;
const tiempoLectura = Math.ceil(palabras / 200); // 200 palabras por minuto
return {
tiempo_lectura: tiempoLectura
};
Extensiones Personalizadas
Directus permite crear extensiones para personalizar el panel y la API:
Tipos de extensiones
- Interface: Componente Vue.js personalizado para edición de campos
- Display: Cómo se muestra un valor en listas
- Panel: Widget para el dashboard de analítica
- Hook: Código que se ejecuta en eventos del servidor
- Endpoint: Endpoints de API personalizados
- Operation: Nuevas operaciones para Flows
Crear una extensión de tipo Hook
# Instalar la CLI de Directus para desarrollo de extensiones
npm install -g @directus/cli
# Crear una nueva extensión
npx create-directus-extension@latest
# Seleccionar:
# - Tipo: hook
# - Lenguaje: TypeScript
# - Nombre: mi-hook-personalizado
cd mi-hook-personalizado
npm install
// src/index.ts - Hook que procesa imágenes al subir
import { defineHook } from '@directus/extensions-sdk';
export default defineHook(({ filter, action }, { services, logger }) => {
// Ejecutar después de subir un archivo
action('files.upload', async ({ payload, key }) => {
logger.info(`Archivo subido: ${payload.filename_disk}`);
// Lógica personalizada: actualizar metadatos
const { FilesService } = services;
const filesService = new FilesService({
schema: await getSchema(),
accountability: { admin: true }
});
await filesService.updateOne(key, {
description: `Subido automáticamente el ${new Date().toISOString()}`
});
});
// Validar antes de crear un artículo
filter('articulos.items.create', async (payload) => {
if (!payload.titulo || payload.titulo.length < 5) {
throw new Error('El título debe tener al menos 5 caracteres');
}
// Generar slug automáticamente
payload.slug = payload.titulo
.toLowerCase()
.replace(/[áàä]/g, 'a')
.replace(/[éèë]/g, 'e')
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '');
return payload;
});
});
# Compilar la extensión
npm run build
# Copiar al directorio de extensiones de Directus
cp -r dist/ /opt/directus/extensions/hooks/mi-hook-personalizado/
# Reiniciar Directus para cargar la extensión
docker compose restart directus
Transformación de Activos
Directus puede transformar imágenes al vuelo mediante parámetros en la URL:
# URL base de un archivo
# http://localhost:8055/assets/UUID_DEL_ARCHIVO
# Transformaciones disponibles vía query params:
# - width, height: redimensionar
# - fit: cover, contain, fill, inside, outside
# - quality: 1-100
# - format: jpg, png, webp, avif
# - focal_point_x, focal_point_y: punto focal para recorte
# Ejemplos de transformaciones
# Thumbnail 400x300 con recorte centrado en WebP
curl "http://localhost:8055/assets/UUID?width=400&height=300&fit=cover&format=webp&quality=80" \
-H "Authorization: Bearer $TOKEN" \
-o imagen-transformada.webp
# Imagen redimensionada manteniendo proporción
curl "http://localhost:8055/assets/UUID?width=800&fit=contain" \
-H "Authorization: Bearer $TOKEN"
Configurar transformaciones predefinidas
# Agregar al .env o docker-compose.yml
ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION: 4096
ASSETS_CONTENT_SECURITY_POLICY: "false"
# Configurar cache de transformaciones
ASSETS_CACHE_TTL: "30d"
Webhooks y Eventos
# Crear un webhook que se dispara al crear artículos
curl -X POST http://localhost:8055/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Notificar sistema externo",
"method": "POST",
"url": "https://api.sistema-externo.com/webhook/directus",
"status": "active",
"data": true,
"actions": ["create", "update"],
"collections": ["articulos"],
"headers": [
{"header": "X-API-Key", "value": "mi-api-key-secreta"},
{"header": "Content-Type", "value": "application/json"}
]
}'
# Listar webhooks configurados
curl http://localhost:8055/webhooks \
-H "Authorization: Bearer $TOKEN" | jq '.data[]'
Solución de Problemas
Error de conexión a la base de datos
# Verificar que PostgreSQL está corriendo
docker compose ps database
# Probar la conexión manualmente
docker exec -it directus-database-1 psql -U directus -d directus -c "\l"
# Ver los logs de Directus al iniciar
docker compose logs directus --tail=50 | grep -i "error\|database\|connection"
Extensión no se carga
# Verificar la estructura del directorio de extensiones
ls -la /opt/directus/extensions/
# La extensión debe estar en la subcarpeta correcta según su tipo:
# hooks/, endpoints/, interfaces/, displays/, panels/, operations/
# Ver los logs de carga de extensiones
docker compose logs directus | grep -i "extension"
# Verificar permisos del directorio
chown -R 1000:1000 /opt/directus/extensions/
Flows no se ejecutan
# Verificar que el Flow está activo
curl http://localhost:8055/flows \
-H "Authorization: Bearer $TOKEN" | jq '.data[] | {name, status}'
# Ver el historial de ejecuciones de un Flow
curl "http://localhost:8055/flows/FLOW_ID" \
-H "Authorization: Bearer $TOKEN" | jq '.data'
Permisos no funcionan como se espera
# Verificar los permisos asignados a un rol
curl "http://localhost:8055/permissions?filter[role][_eq]=ROL_ID" \
-H "Authorization: Bearer $TOKEN" | jq '.data'
# Probar con el token de un usuario con ese rol
TOKEN_EDITOR=$(curl -s -X POST http://localhost:8055/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"password"}' \
| jq -r '.data.access_token')
# Probar acceso con el token del editor
curl http://localhost:8055/items/articulos \
-H "Authorization: Bearer $TOKEN_EDITOR"
Conclusión
Directus sobresale como headless CMS cuando los requisitos van más allá de un CMS estándar: permisos granulares por campo, automatización sin código con Flows, extensiones personalizadas y soporte multi-base-de-datos lo hacen especialmente valioso en entornos enterprise. Su capacidad para conectarse a bases de datos existentes sin modificar su estructura lo convierte en una capa de gestión que puede superponer sobre infraestructura de datos ya existente, sin migración ni riesgo de pérdida de datos.


