DEV Community

Cover image for Connecting TanStack Start to Directus with the SDK — Type-Safe Data Fetching in One File
Wade Thomas
Wade Thomas

Posted on

Connecting TanStack Start to Directus with the SDK — Type-Safe Data Fetching in One File

If you're using Directus as your headless CMS and TanStack Start for your frontend, you don't need to write manual fetch calls or build your own auth headers. The Directus SDK handles all of it cleanly.

Here's how I structure a single directus.ts file that covers authentication, typed data fetching, filtering, CRUD operations and file uploads.

Installing the SDK

npm install @directus/sdk
Enter fullscreen mode Exit fullscreen mode

Setting Up the Client

Create the client once and export it. Passing your schema type as a generic is what unlocks end-to-end type safety across every request.

import type { Product, Navigation, CartItem, Order } from '@/types';
import {
  authentication,
  createDirectus,
  rest,
  readItems,
  createItem,
  updateItem,
  deleteItem,
  readMe,
  updateMe,
  deleteUser,
  uploadFiles,
  registerUser as registerUserDirectus,
} from '@directus/sdk';

const directusUrl =
  import.meta.env.VITE_DIRECTUS_URL ??
  process.env.VITE_DIRECTUS_URL ??
  'https://your-directus-url.com';

const directus = createDirectus(directusUrl)
  .with(authentication('session', { credentials: 'include' }))
  .with(rest({ credentials: 'include' }));
Enter fullscreen mode Exit fullscreen mode

Using authentication('session') with credentials: 'include' means cookies are handled automatically — no manual token management needed.

Fetching Data with Full Type Safety

Each collection gets its own exported async function with a typed return value.

export async function getProducts(): Promise<Product[]> {
  const items = await directus.request(readItems('products'));
  return items as Product[];
}
Enter fullscreen mode Exit fullscreen mode

Filtering is First-Class

The SDK's filter syntax maps directly to Directus's query engine — no raw query strings, no URL building.

export async function getProductsByCategory(
  category: string
): Promise<Product[]> {
  const items = await directus.request(
    readItems('products', {
      filter: { category: { _eq: category } },
    })
  );
  return items as Product[];
}
Enter fullscreen mode Exit fullscreen mode

What Else is Covered

The same client and pattern covers the full CRUD surface:

  • readItems — fetch collections
  • createItem — insert records
  • updateItem — update records
  • deleteItem — delete records
  • uploadFiles — handle file uploads
  • readMe / updateMe / deleteUser — user profile management
  • registerUser — user registration

All importable directly from @directus/sdk, all typed.

Using it in a TanStack Start Loader

TanStack Start's file-based routing and loader pattern pairs perfectly with this setup. Data is fetched server-side and ready before the component renders.

export const Route = createFileRoute('/products')({
  loader: () => getProducts()
});
Enter fullscreen mode Exit fullscreen mode

No boilerplate, no custom wrappers, no type assertions. One file, full coverage.


If you're evaluating Directus as a headless CMS for a TanStack Start project this setup gets you up and running quickly with a clean, maintainable data layer from day one.

Top comments (0)