Skip to main content
Documentation

Quickstart

Scaffold, run, and deploy a real-time app.

On this page

This guide takes you from npm create to a deployed app at <your-app>.app.space. You'll need a supported Node line (see installation) and a GitHub or Google account for sign-in.

For prerequisites and account setup, see installation. For a tour of the files the scaffolder generates, see project structure.

1. Scaffold an app#

The create-deepspace package bootstraps a new project with the SDK preinstalled, a worker wired to a Durable Object, and a Vite-powered React frontend.

npm create deepspace@latest my-app
cd my-app
bash

The scaffolder is non-interactive by default and prints a list of files it created. Pass --interactive if you want a prompt-driven wizard.

If you are already signed in, the last thing the scaffolder does is register the app under that account - it names the account and the plane as it goes. See which account the app registers under if more than one login could be active on this machine, or if you want to scaffold now and register later with --no-register.

2. Start the dev server#

npx deepspace dev start
bash

This runs Vite and the worker together and opens http://localhost:5173 with hot-module reload. The CLI also writes an app-owner JWT and platform URLs to .dev.vars, so your local worker can call the live auth, storage, and integration services.

Pass --port to run multiple apps in parallel:

npx deepspace dev start --port 5180
bash

3. Sign in to the CLI#

You'll need a DeepSpace account to deploy. One-time setup:

npx deepspace auth login
bash

This opens your browser for GitHub or Google OAuth and stores a long-lived session at ~/.deepspace/session. DeepSpace accounts are OAuth-only - there is no password. The same session covers every app on the machine, and every later command refreshes from it on its own.

Check status anytime with:

npx deepspace auth whoami
bash

Manage your account, deployed apps, and billing at dashboard.deep.space.

4. Make it yours#

Open the project in your editor.

Key files:

  • src/pages/index.tsx - the static landing page at /. Edit the hero copy.
  • src/pages/(app)/home.tsx - the signed-in home route at /home.
  • src/schemas.ts - collection schemas. Add a new collection alongside the seeded users and settings.
  • src/pages/(app)/_layout.tsx - the provider stack. Auth and realtime data are already wired.
  • worker.ts - the Hono worker. Add custom routes here.
  • index.html - change the <title> and data-theme attribute to pick a preset.

Where a page goes decides what it can do#

Routing is file-based, and the folder a page sits in determines which providers wrap it. There are three tiers:

LocationProvidersUse for
src/pages/*.tsxNoneStatic pages - marketing, legal, docs. No auth fetch, no WebSocket.
src/pages/(app)/*.tsxDeepSpaceAuthProvider + RecordProviderDynamic pages. useAuth, useQuery, useMutations, presence all work. Sign-in not required.
src/pages/(app)/(protected)/*.tsxThe above, plus <AuthGate>Pages that require sign-in.

Folders in parentheses are generouted route groups - they apply a layout without appearing in the URL. So (app)/(protected)/todos.tsx serves at /todos.

Try adding a todos collection. For the full schema reference, see worker schemas.

// src/schemas/todos-schema.ts
import type { CollectionSchema } from 'deepspace/worker'

export const todosSchema: CollectionSchema = {
  name: 'todos',
  columns: [
    { name: 'title', storage: 'text', interpretation: 'plain' },
    { name: 'completed', storage: 'number', interpretation: { kind: 'boolean' } },
  ],
  permissions: {
    member: { read: true, create: true, update: 'own', delete: 'own' },
    admin: { read: true, create: true, update: true, delete: true },
  },
}
ts
// src/schemas.ts
import type { CollectionSchema } from 'deepspace/worker'
import { todosSchema } from './schemas/todos-schema'
import { usersSchema } from './schemas/users-schema'
import { settingsSchema } from './schemas/admin-schema'

export const schemas: CollectionSchema[] = [usersSchema, settingsSchema, todosSchema]
ts

Then read and write from the UI using useQuery and useMutations:

// src/pages/(app)/(protected)/todos.tsx
import { useQuery, useMutations } from 'deepspace'

type Todo = { title: string; completed: boolean }

export default function TodosPage() {
  const { records, status } = useQuery<Todo>('todos', { orderBy: 'createdAt' })
  const { create, put, remove } = useMutations<Todo>('todos')

  if (status === 'loading') return <p>Loading…</p>

  return (
    <>
      <ul>
        {records.map((r) => (
          <li key={r.recordId}>
            <input
              type="checkbox"
              checked={r.data.completed}
              onChange={(e) => put(r.recordId, { completed: e.target.checked })}
            />
            {r.data.title}
            <button onClick={() => remove(r.recordId)}>Delete</button>
          </li>
        ))}
      </ul>
      <button onClick={() => create({ title: 'New todo', completed: false })}>
        Add
      </button>
    </>
  )
}
tsx

The page lives at /todos - generouted picks it up automatically. Because it sits under (app)/(protected)/, it inherits <RecordProvider> from (app)/_layout.tsx (so the hooks above work) and <AuthGate> from (app)/(protected)/_layout.tsx (so it requires sign-in). See authentication. Save and the page hot-reloads.

5. Deploy#

Commit first - a deploy records the commit it ships:

git add -A && git commit -m "add todos"
npx deepspace deploy
bash

The CLI bundles the worker, uploads it to Cloudflare Workers for Platforms, syncs any secrets from .dev.vars, and registers your subdomain. The deployment reference covers the full pipeline. Within a few seconds, your app is live at:

https://<your-app>.app.space

Visit the URL, sign in, and add a todo. Open the same URL in a second tab - edits sync between them instantly.

Next steps#

You have a working app. Where to go from here: