10 KiB
Executable File
title, description, navigation
| title | description | navigation | ||
|---|---|---|---|---|
| Sexy - Headless CMS Runway | We make content management look good! |
|
"We make content management look good!" - Directus + SvelteKit
What's This All About?
This is your full-stack content management system! A headless CMS (Directus) paired with a blazing-fast SvelteKit frontend. It's like WordPress if WordPress went to design school, hit the gym, and learned to code properly. All at sexy.pivoine.art because why not make content management... sexy? 😎
The Power Couple
:icon{name="lucide:palette"} Directus API
Container: sexy_api
Image: directus/directus:11.12.0
Port: 8055
Home: https://sexy.pivoine.art/api
Directus is the headless CMS that doesn't make you cry:
- :icon{name="lucide:bar-chart"} Database-First: Works with your existing database
- :icon{name="lucide:ethernet-port"} Admin Panel: Beautiful UI out of the box
- :icon{name="lucide:plug"} REST + GraphQL: Choose your flavor
- :icon{name="lucide:image"} Asset Management: Images, videos, files - all handled
- :icon{name="lucide:users"} User Roles: Granular permissions
- :icon{name="lucide:refresh-cw"} Real-time: WebSocket support for live updates
- :icon{name="lucide:palette"} Customizable: Extensions, hooks, custom fields
- :icon{name="lucide:lock-keyhole"} Auth: Built-in user management and SSO
:icon{name="lucide:zap"} SvelteKit Frontend
Container: sexy_frontend
Image: node:22
Port: 3000
Home: https://sexy.pivoine.art
The face of your content:
- :icon{name="lucide:rocket"} Lightning Fast: Svelte's magic compilation
- :icon{name="lucide:target"} SEO Friendly: Server-side rendering
- :icon{name="lucide:phone"} Responsive: Mobile-first design
- :icon{name="lucide:palette"} Beautiful: Because sexy.pivoine.art deserves it
- :icon{name="lucide:refresh-cw"} Real-time Updates: Live data from Directus
- :icon{name="lucide:sparkles"} Styled: Tailwind CSS + custom design
Architecture
User Request (sexy.pivoine.art)
↓
Traefik
├─ /api/* → Directus API (Backend)
└─ /* → SvelteKit (Frontend)
The magic:
- Frontend requests
/api/items/posts - Traefik strips
/apiprefix - Routes to Directus
- Returns JSON
- Frontend renders beautifully
Configuration Breakdown
Directus Environment
Database:
DB_CLIENT=pg # PostgreSQL ftw!
DB_HOST=postgres
DB_DATABASE=directus
Cache (Redis for speed):
CACHE_ENABLED=true
CACHE_STORE=redis
REDIS=redis://redis:6379
WebSockets (Real-time magic):
WEBSOCKETS_ENABLED=true
CORS (Frontend can talk to API):
CORS_ENABLED=true
CORS_ORIGIN=https://sexy.pivoine.art
SESSION_COOKIE_DOMAIN=sexy.pivoine.art
Extensions (Custom functionality):
EXTENSIONS_PATH=./extensions
DIRECTUS_BUNDLE=/var/www/sexy.pivoine.art/packages/bundle
Frontend Setup
Running from /var/www/sexy.pivoine.art:
# Built SvelteKit app
node build/index.js
First Time Setup :icon
1. Create Database
docker exec data_postgres createdb -U your_db_user directus
2. Start the Stack
docker compose up -d
3. Access Directus Admin
URL: https://sexy.pivoine.art/api/admin
Email: Your ADMIN_EMAIL
Password: Your ADMIN_PASSWORD
4. Create Your First Collection
-
Go to Settings → Data Model
-
Click "Create Collection"
-
Name it (e.g., "posts")
-
Add Fields:
- Title (String)
- Content (WYSIWYG)
- Author (Many-to-One User)
- Published Date (DateTime)
- Featured Image (Image)
-
Save and Create Item!
Using the Admin Panel :icon
Content Management
Create Items:
- Navigate to your collection
- Click "+" button
- Fill in fields
- Save as draft or publish
Media Library:
- Upload images, videos, PDFs
- Organize in folders
- Generate thumbnails automatically
- Serve optimized versions
User Management:
- Create editors, authors, admins
- Set granular permissions
- SSO integration available
Data Model
Field Types:
- :icon{name="lucide:file-text"} Text (String, Text, Markdown) -- :icon{name="lucide:arrow-up-0-1"} Numbers (Integer, Float, Decimal)
- :icon{name="lucide:calendar"} Dates (Date, DateTime, Time)
- :icon{name="lucide:check"} Booleans & Toggles
- :icon{name="lucide:palette"} JSON & Code
- :icon{name="lucide:link"} Relations (O2M, M2O, M2M)
- :icon{name="lucide:image"} Files & Images -- :icon{name="lucide:map-pin"} Geolocation
API Usage :icon
REST API
Get All Posts:
curl https://sexy.pivoine.art/api/items/posts
Get Single Post:
curl https://sexy.pivoine.art/api/items/posts/1
Create Post (Auth required):
curl -X POST https://sexy.pivoine.art/api/items/posts \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "My First Post",
"content": "Hello World!"
}'
Filter & Sort:
curl "https://sexy.pivoine.art/api/items/posts?filter[status][_eq]=published&sort=-date"
GraphQL
query {
posts {
id
title
content
author {
first_name
last_name
}
}
}
Authentication
Login:
curl -X POST https://sexy.pivoine.art/api/auth/login \
-d '{"email": "user@example.com", "password": "secret"}'
Returns access token for authenticated requests.
Frontend Integration
Fetching in SvelteKit
// src/routes/blog/+page.js
export async function load({ fetch }) {
const res = await fetch('https://sexy.pivoine.art/api/items/posts');
const { data } = await res.json();
return {
posts: data
};
}
<!-- src/routes/blog/+page.svelte -->
<script>
export let data;
</script>
{#each data.posts as post}
<article>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
{/each}
Image Optimization
Directus automatically generates thumbnails:
<img
src="https://sexy.pivoine.art/api/assets/{id}?width=800&quality=80"
alt="Featured"
>
Real-Time Updates :icon
WebSocket Connection
import { createDirectus, realtime } from '@directus/sdk';
const client = createDirectus('https://sexy.pivoine.art/api')
.with(realtime());
client.subscribe('posts', {
event: 'create',
query: {
fields: ['*']
}
}, (message) => {
console.log('New post!', message);
});
Extensions & Customization :icon
Custom Hooks
// extensions/hooks/notify-on-publish/index.js
export default ({ filter }) => {
filter('posts.items.create', async (payload) => {
// Send notification when post created
await sendNotification(payload);
return payload;
});
};
Custom Endpoints
// extensions/endpoints/stats/index.js
export default (router) => {
router.get('/', async (req, res) => {
const stats = await calculateStats();
res.json(stats);
});
};
Custom Panels
Create custom admin panels with Vue.js!
Volumes & Data
Uploads Directory
./uploads → /directus/uploads
All uploaded files stored here.
Extensions Bundle
/var/www/sexy.pivoine.art/packages/bundle
Custom extensions and functionality.
Ports & Networking
| Service | Internal Port | External Access |
|---|---|---|
| Directus API | 8055 | /api/* via Traefik |
| Frontend | 3000 | /* via Traefik |
Content Workflows
Blog Post Workflow
- Draft: Writer creates post
- Review: Editor reviews content
- Approve: Admin approves
- Schedule: Set publish date
- Publish: Goes live automatically
User Permissions
- Admin: Full access
- Editor: Edit content, manage media
- Author: Create own posts
- Public: Read published content
Performance Optimization :icon
Caching Strategy
// Redis cache for API responses
CACHE_AUTO_PURGE=true // Auto-clear on changes
CACHE_TTL=300 // 5 minutes
Image Optimization
- Automatic WebP conversion
- Lazy loading
- Responsive images
- CDN-ready URLs
Database Queries
- Indexed fields
- Query result caching
- Connection pooling
Security Best Practices :icon
- Change Default Password: First thing!
- API Access Tokens: Use tokens, not passwords
- CORS Configuration: Only allow your domain
- Rate Limiting: Protect against abuse
- File Upload Validation: Check file types
- Regular Backups: Both database and uploads
Troubleshooting
Q: Can't access admin panel?
A: Check ADMIN_EMAIL and ADMIN_PASSWORD in .env
Q: API returns 401?
A: Need authentication token for private collections
Q: Images not loading?
A: Check uploads volume is mounted correctly
Q: Frontend can't fetch API?
A: Verify CORS settings and PUBLIC_URL
Q: Real-time not working?
A: Check WEBSOCKETS_ENABLED=true and wss:// connection
Common Use Cases
Blog Platform
- Posts, authors, categories
- Comments system
- SEO optimization
- RSS feed
E-commerce
- Products catalog
- Inventory management
- Order processing
- Customer data
Portfolio Site
- Project showcase
- Case studies
- Client testimonials
- Contact forms
Documentation
- Articles & guides
- Search functionality
- Version control
- Multi-language support
Why This Stack is Sexy :icon
- :icon{name="lucide:sparkles"} Developer Experience: Joy to work with
- :icon{name="lucide:rocket"} Performance: Fast out of the box
- :icon{name="lucide:palette"} Design: Beautiful admin interface
- :icon{name="lucide:wrench"} Flexibility: Customize everything
- :icon{name="lucide:phone"} Modern: Built with latest tech
- :icon{name="lucide:smile"} Open Source: Free forever
- :icon{name="lucide:dumbbell"} Production Ready: Powers serious sites
Resources
"Content management should feel like art, not work." - Sexy Philosophy :icon{name="lucide:sparkles"}:icon{name="lucide:sparkles"}