Directus
Database-first headless CMS. Instantly turns existing databases into APIs.
Headless CMS
Directus
Overview
Directus is an open-source headless CMS that connects to existing SQL databases and instantly generates REST and GraphQL APIs. It combines an intuitive admin interface (Data Studio) that's accessible to non-technical users with powerful APIs, providing the flexibility to handle any type of project. From data model design to content management and API delivery, everything is achieved on a single platform.
Details
Directus adopts a "Database First" approach, leveraging new or existing SQL databases as-is. It supports major databases including PostgreSQL, MySQL, SQLite, OracleDB, CockroachDB, MariaDB, and MS-SQL, allowing deployment without migration. Built with TypeScript and based on Node.js and Vue.js, it's a completely open-source platform.
Key Features:
- Instant API Generation: Auto-generates REST/GraphQL APIs from existing DB in under 90 seconds
- Data Studio: User-friendly Vue.js admin interface for non-technical users
- Data Modeling: Advanced data structures including Many-to-Any (M2A) relationships
- Extensibility: Complete customization through Extensions SDK
- Built-in Features: Authentication, caching, image transformation, aggregation, filtering
- Flows: Event-driven data processing and task automation
- Access Control: Granular role-based permissions
- Real-time Support: Real-time updates via WebSocket
Advantages and Disadvantages
Advantages
- Can leverage existing databases as-is
- Flexibility not constrained by database structure
- User-friendly UI for both technical and non-technical users
- Completely open source (GPL-3.0)
- Extensive database support
- Powerful extension system
- High API performance
- Supports both self-hosting and cloud deployment
Disadvantages
- Requires database knowledge
- No support for NoSQL databases
- Complex initial setup
- Requires pre-defining content types
- Limited CMS-specific features
- Somewhat steep learning curve
Reference Pages
- Official Website
- Official Documentation
- GitHub Repository
- API Documentation
- Extensions SDK
- Directus Cloud
Code Examples
1. Hello World (Basic Setup)
# Install Directus (using Docker)
docker run -p 8055:8055 \
-e ADMIN_EMAIL="[email protected]" \
-e ADMIN_PASSWORD="password" \
-e KEY="your-random-key" \
-e SECRET="your-random-secret" \
directus/directus
# Install with NPM
npm init directus-project my-project
cd my-project
npx directus bootstrap
npx directus start
// Initialize Directus SDK
import { createDirectus, rest, authentication } from '@directus/sdk';
// Create client
const client = createDirectus('http://localhost:8055')
.with(authentication())
.with(rest());
// Login
await client.login('[email protected]', 'password');
// Fetch data
const articles = await client.request(
readItems('articles', {
fields: ['id', 'title', 'content', 'author.name'],
limit: 10
})
);
2. Content Management
// Create collections and fields (using Admin API)
import { createCollection, createField } from '@directus/sdk';
// Create articles collection
await client.request(
createCollection({
collection: 'articles',
meta: {
collection: 'articles',
icon: 'article',
display_template: '{{title}}'
}
})
);
// Add fields
await client.request(
createField('articles', {
field: 'title',
type: 'string',
meta: {
interface: 'input',
display: 'formatted-value',
required: true
}
})
);
// Create relation
await client.request(
createField('articles', {
field: 'author',
type: 'uuid',
meta: {
interface: 'select-dropdown-m2o',
display: 'related-values',
display_options: {
template: '{{first_name}} {{last_name}}'
}
},
schema: {
foreign_key_table: 'directus_users',
foreign_key_column: 'id'
}
})
);
3. API Operations
// CRUD operations
import {
readItems,
readItem,
createItem,
updateItem,
deleteItem
} from '@directus/sdk';
// Fetch multiple items with filtering
const publishedArticles = await client.request(
readItems('articles', {
filter: {
status: {
_eq: 'published'
},
date_published: {
_lte: '$NOW'
}
},
sort: ['-date_published'],
limit: 20
})
);
// Fetch single item
const article = await client.request(
readItem('articles', 'article-id')
);
// Create item
const newArticle = await client.request(
createItem('articles', {
title: 'New Article',
content: 'Article content...',
status: 'draft'
})
);
// Update item
await client.request(
updateItem('articles', 'article-id', {
status: 'published',
date_published: new Date()
})
);
// Aggregation and grouping
const stats = await client.request(
readItems('articles', {
aggregate: {
count: ['id'],
avg: ['views']
},
groupBy: ['status']
})
);
4. Authentication Setup
// Configure authentication
const client = createDirectus('http://localhost:8055')
.with(authentication('cookie', { credentials: 'include' }))
.with(rest());
// Login
await client.login('[email protected]', 'password');
// Get token
const token = await client.getToken();
// Use static token
import { staticToken } from '@directus/sdk';
const publicClient = createDirectus('http://localhost:8055')
.with(staticToken('your-static-token'))
.with(rest());
// Implement custom storage
class CustomStorage {
get() {
return JSON.parse(localStorage.getItem('directus-auth'));
}
set(data) {
localStorage.setItem('directus-auth', JSON.stringify(data));
}
}
const client = createDirectus('http://localhost:8055')
.with(authentication('json', { storage: new CustomStorage() }))
.with(rest());
// Enable two-factor authentication
await client.request(enableTwoFactor('secret', 'otp'));
5. Plugins and Extensions
// Create custom endpoint
// extensions/endpoints/custom/index.js
export default {
id: 'custom',
handler: (router, { services, env }) => {
const { ItemsService } = services;
router.get('/stats', async (req, res) => {
const articlesService = new ItemsService('articles', {
schema: req.schema,
accountability: req.accountability
});
const count = await articlesService.readByQuery({
aggregate: {
count: ['id']
}
});
res.json({ total_articles: count });
});
}
};
// Create custom hook
// extensions/hooks/audit/index.js
export default ({ filter, action }, { services, database }) => {
const { ItemsService } = services;
filter('items.create', async (input, { collection }) => {
input.created_by = input.accountability?.user;
return input;
});
action('items.create', async ({ payload, collection }) => {
const auditService = new ItemsService('audit_log', {
schema: await getSchema()
});
await auditService.createOne({
collection,
action: 'create',
user: payload.accountability?.user,
timestamp: new Date()
});
});
};
// Create custom interface
// extensions/interfaces/custom-input/index.js
import InterfaceComponent from './interface.vue';
export default {
id: 'custom-input',
name: 'Custom Input',
icon: 'box',
component: InterfaceComponent,
options: [
{
field: 'placeholder',
name: 'Placeholder',
type: 'string'
}
]
};
6. Deployment and Production Setup
// Environment variables configuration
// .env
KEY=your-random-key
SECRET=your-random-secret
DB_CLIENT=postgres
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=directus
DB_USER=postgres
DB_PASSWORD=password
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=password
PUBLIC_URL=https://api.example.com
CORS_ENABLED=true
CORS_ORIGIN=https://app.example.com
// PM2 startup configuration
// ecosystem.config.js
module.exports = {
apps: [{
name: 'directus',
script: 'npx',
args: 'directus start',
env: {
NODE_ENV: 'production',
PORT: 8055
}
}]
};
// Production setup with Docker
// docker-compose.yml
version: '3'
services:
database:
image: postgres:15
environment:
POSTGRES_USER: directus
POSTGRES_PASSWORD: directus
POSTGRES_DB: directus
volumes:
- ./data/database:/var/lib/postgresql/data
directus:
image: directus/directus:latest
ports:
- 8055:8055
volumes:
- ./uploads:/directus/uploads
- ./extensions:/directus/extensions
environment:
KEY: ${KEY}
SECRET: ${SECRET}
DB_CLIENT: postgres
DB_HOST: database
DB_PORT: 5432
DB_DATABASE: directus
DB_USER: directus
DB_PASSWORD: directus
ADMIN_EMAIL: ${ADMIN_EMAIL}
ADMIN_PASSWORD: ${ADMIN_PASSWORD}
depends_on:
- database
// Nginx reverse proxy configuration
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://localhost:8055;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}