Skip to main content

Command Palette

Search for a command to run...

Why I Chose Directus as My Backend

Setup, Collections, Roles and Docker Compose

Published
5 min read
Why I Chose Directus as My Backend
W
I'm a full stack developer interested in anything, TanStack Start, Directus, Tailwindcss v4, Shadcn, Zustand and Coolify.

When I started building the Demo Store — a full stack e-commerce app with TanStack Start — I needed a backend that could handle data, authentication, file storage, and an admin interface without stitching together multiple services.

Directus ticked every box. Here's why I chose it, how I set it up, and what it looks like in practice.

How Directus Compares to Other CMS Options

There are plenty of headless CMS options out there. Here's how Directus stands out:

Directus Strapi Contentful Sanity
Self-hostable
Free to use ✅ under $5M revenue ✅ open source ❌ paid tiers ❌ paid tiers
SQL DB support ✅ any existing SQL DB ✅ PostgreSQL, MySQL, MariaDB, SQLite
Built-in auth
REST + GraphQL
Admin dashboard

Strapi is a strong alternative and supports multiple SQL databases too. The key difference for me was that Directus connects directly to any existing database — you point it at a database you already have and it works. There's no schema migration system to manage and no framework-specific setup. Contentful and Sanity are cloud-only, which means you don't own your data and you're subject to their pricing as you scale.

Setting Up Directus with Docker Compose

I run Directus on a VPS managed by Coolify. The entire setup lives in a single Docker Compose file. Here's the full config I use for the demo store:

services:
  directus:
    image: 'directus/directus:11.15.4'
    ports:
      - '8055:8055'
    volumes:
      - './uploads:/directus/uploads'
      - './extensions:/directus/extensions'
    environment:
      SECRET: '${SECRET}'
      MARKETPLACE_TRUST: all

      # Database
      DB_CLIENT: pg
      DB_HOST: '${DB_HOST}'
      DB_PORT: '5432'
      DB_DATABASE: '${DB_DATABASE}'
      DB_USER: '${DB_USER}'
      DB_PASSWORD: '${DB_PASSWORD}'

      # Redis Cache
      CACHE_ENABLED: 'false'
      CACHE_AUTO_PURGE: 'true'
      CACHE_STORE: redis
      REDIS: '${REDIS_URL}'

      # CORS
      CORS_ENABLED: 'true'
      CORS_ORIGIN: '${CORS_ORIGIN}'
      CORS_CREDENTIALS: 'true'

      # Email via Resend SMTP
      EMAIL_TRANSPORT: smtp
      EMAIL_SMTP_HOST: smtp.resend.com
      EMAIL_SMTP_PORT: 465
      EMAIL_SMTP_USER: resend
      EMAIL_SMTP_PASSWORD: '${EMAIL_SMTP_PASSWORD}'
      EMAIL_SMTP_SECURE: 'true'
      EMAIL_FROM: contact@yourdomain.com

      # Auth
      USER_REGISTER_REQUIRE_EMAIL_VERIFICATION: 'true'
      USER_REGISTER_URL_ALLOW_LIST: '${USER_REGISTER_URL_ALLOW_LIST}'
      PASSWORD_RESET_URL_ALLOW_LIST: '${PASSWORD_RESET_URL_ALLOW_LIST}'

      # Google OAuth
      AUTH_PROVIDERS: google
      AUTH_GOOGLE_DRIVER: openid
      AUTH_GOOGLE_ISSUER_URL: 'https://accounts.google.com'
      AUTH_GOOGLE_CLIENT_ID: '${AUTH_GOOGLE_CLIENT_ID}'
      AUTH_GOOGLE_CLIENT_SECRET: '${AUTH_GOOGLE_CLIENT_SECRET}'
      AUTH_GOOGLE_IDENTIFIER_KEY: email
      AUTH_GOOGLE_ALLOW_PUBLIC_REGISTRATION: 'true'
      AUTH_GOOGLE_DEFAULT_ROLE_ID: '${AUTH_GOOGLE_DEFAULT_ROLE_ID}'
      AUTH_GOOGLE_REDIRECT_ALLOW_LIST: '${AUTH_GOOGLE_REDIRECT_ALLOW_LIST}'
      AUTH_GOOGLE_MODE: session

      # Session & Cookies
      SESSION_COOKIE_SECURE: 'true'
      SESSION_COOKIE_SAME_SITE: lax
      REFRESH_TOKEN_COOKIE_SECURE: 'true'
      REFRESH_TOKEN_COOKIE_SAME_SITE: lax

      # Extensions
      EXTENSIONS_AUTO_RELOAD: 'true'
      EXTENSIONS_PATH: /directus/extensions

A few things worth noting:

  • PostgreSQL is the database — DB_CLIENT: pg tells Directus to use it

  • Resend handles all transactional email via SMTP — verification emails, password resets

  • Google OAuth is configured via OpenID Connect — users can register and log in with their Google account

  • Session cookies are secure and SameSite lax — works cleanly with TanStack Start on a separate domain

  • All sensitive values are environment variables — never hardcoded

Note: In my setup PostgreSQL runs as a separate service managed independently by Coolify — that's why it's not included in this Compose file. If you prefer to keep everything together, you can add PostgreSQL directly to the same file:

  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: '${DB_DATABASE}'
      POSTGRES_USER: '${DB_USER}'
      POSTGRES_PASSWORD: '${DB_PASSWORD}'
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Then set DB_HOST to postgres in your Directus environment variables to point it at the service name. Either approach works — separate or combined is a matter of preference.

The Admin Dashboard and Collections

Once Directus is running, you access the admin dashboard at your Directus URL. This is where you build your data model visually.

For the demo store I created the following collections:

  • products — name, description (WYSIWYG), price, sale price, category, images, slug

  • categories — name, slug, description

  • orders — user, items, total, status

  • order_items — order, product, quantity, price

  • navigation — links and structure for the site nav

Each collection maps directly to a PostgreSQL table. Directus generates the REST and GraphQL API automatically. No boilerplate, no migrations to write by hand.

The WYSIWYG editor on the description field outputs HTML — I cover how to render that safely in React in a later post in this series.

Roles and Permissions

This is where Directus really shines for production apps. Every collection and field can have granular permissions set per role.

For the demo store I set up three roles:

Public — unauthenticated visitors

  • Can read products, categories, navigation

  • Cannot read orders, order_items, or any user data

Authenticated User — logged in customers

  • Can read all public collections

  • Can read and create their own orders and order_items

  • Can read and update their own user profile

  • Cannot read other users' data

Admin — full access

  • Full CRUD on all collections

  • Access to the Directus admin dashboard

The permissions are configured in the Directus admin panel under Settings → Roles & Permissions. You set them visually — no code required. Each role gets a matrix of read, create, update, delete permissions per collection, and you can even restrict access to specific fields within a collection.

What Directus Handles For the Demo Store

  • ✅ All product and category data via REST API

  • ✅ User registration with email verification

  • ✅ Login with email/password and Google OAuth

  • ✅ Password reset via email

  • ✅ Order management

  • ✅ File and image storage and serving

  • ✅ Role-based access control

  • ✅ Admin dashboard for managing all content

All from a single self-hosted instance running on a $20/month VPS.


In the next post I'll cover how I connect TanStack Start to Directus using the SDK — setting up the client, typed data fetching, and filtering collections.

➡️ Next: Connecting TanStack Start to Directus with the SDK

Building Full Stack Apps with TanStack Start and Directus

Part 3 of 4

This series highlights the different techniques used to create a full stack eCommerce app using TanStack Start, Directus, Tailwindcss v4, Shadcn, Zustand and Coolify.

Up next

Building a Full Stack E-Commerce App with TanStack Start and Directus

Series Introduction