Skip to content

Step 1: Project Setup

import { Steps, FileTree, Aside } from ‘@astrojs/starlight/components’;

Run create-cruz-app to scaffold a new CruzJS project:

Terminal window
npx create-cruz-app my-todos
cd my-todos
npm install

This scaffolds a complete application with @cruzjs/core (framework runtime), @cruzjs/start (auth, UI components, organizations), React Router v7, Drizzle ORM, and Inversify dependency injection.

Terminal window
cruz dev

Open http://localhost:5000. You should see the CruzJS welcome page.

The dev server uses Vite with Cloudflare Workers compatibility via Miniflare — your local environment behaves exactly like production.

Here is the directory layout you start with:

- src/ - database/ - schema.ts **Barrel export — every table goes here** - migrations/ Drizzle SQL migration files - features/ Your app's feature modules - start/ Auth, organizations, settings (from @cruzjs/start) - routes/ React Router routes - root.tsx Root layout - _index.tsx Home page - server.cloudflare.ts **App entry point — registers modules** - routes.ts **React Router route registration** - cruz.config.ts Cloudflare bindings, env vars, environments - wrangler.toml Generated by `cruz init` — do not edit by hand

The two most important files are:

This is the Cloudflare Worker entry point. It bootstraps the DI container, registers modules, and handles tRPC + page requests:

import * as schema from './database/schema';
import { createCruzApp } from '@cruzjs/core';
import { StartModule } from '@cruzjs/start/start.module';
export default createCruzApp({
schema,
modules: [
StartModule, // auth, sessions, org management
// your modules go here
],
pages: () => import('virtual:react-router/server-build'),
});

Every feature module you create gets added to modules: [...] here.

The Drizzle migration runner discovers tables by reading this barrel file:

// Re-export framework tables
export * from '@cruzjs/start/database/schema';
// Your feature tables go here
// export * from '../features/todos/todos.schema';

Every *.schema.ts file must be re-exported here or Drizzle won’t generate migrations for it.

Declares Cloudflare bindings (D1, KV, R2) and per-environment variables:

import { defineConfig } from '@cruzjs/cli/config';
export default defineConfig({
name: 'my-todos',
bindings: {
d1: true, // D1 database (always required)
kv: true, // KV for sessions
},
environments: {
production: {
vars: {
APP_URL: 'https://my-todos.pages.dev',
},
},
},
});

The scaffold generates several files that require a small amount of wiring. Complete these steps before continuing.

Replace the generated file with this version that sends the auth token on every request:

import { createTRPCReact } from '@trpc/react-query';
import { createTRPCClientFactory, createDefaultQueryClient } from '@cruzjs/core/trpc/client';
import type { AppRouter } from './router';
export const trpc = createTRPCReact<AppRouter>();
export function createTRPCClient() {
return createTRPCClientFactory(trpc);
}
export function createQueryClient() {
return createDefaultQueryClient();
}

createTRPCClientFactory reads the session token from localStorage and adds an Authorization: Bearer header to every tRPC request. Without this, all protected procedures return 401.

Add two registration calls at the top of the file so CruzJS framework components can access your tRPC instance:

import { CruzProviders } from '@cruzjs/core/framework/components';
import { registerTRPC } from '@cruzjs/core/trpc/client';
import { registerOrgTRPC } from '@cruzjs/start/orgs/org.hooks';
import { trpc, createTRPCClient, createQueryClient } from '@/trpc/client';
import { theme } from '@/theme';
// Register tRPC so @cruzjs/* package components can access the hooks
registerTRPC(trpc);
registerOrgTRPC(trpc);
// ... rest of root.tsx

Add @cruzjs/core, @cruzjs/start, and @cruzjs/pro to optimizeDeps.exclude. Without this, Vite pre-bundles these packages as separate module copies, so registerTRPC() sets state in one copy while framework components read from another:

optimizeDeps: {
exclude: ['inversify', '@cruzjs/core', '@cruzjs/start', '@cruzjs/pro'],
},

The scaffold includes a working registration and sign-in flow. Visit http://localhost:5000/auth/register and create an account.

You should be redirected to an org setup screen and then the dashboard. This confirms the auth stack and database are working correctly.


Next: Define the database schema →