Day-to-day DevStride development runs through the ds CLI (./ds from the repo root). This page covers the loop you'll use constantly once your stage is bootstrapped: starting the backend and UI, scaffolding new CQRS commands/queries, running migrations, and managing local data.
./setup built for your machine (its own database, config and first deploy, recorded in the machine's secrets bundle). See Your AWS stage for what it is, and Getting Started for migrations, config and Stripe on it.ds run backend
This runs pnpm install, cleans up any orphaned CloudFormation stacks left over from a previous sst.config.ts change, and then starts pnpm exec sst dev — SST's live-lambda dev mode for the Hono backend. Leave this running in its own terminal; Ctrl+C stops it cleanly.
ds run backend does not start Docker or any local services. DynamoDB access always points at real AWS in normal (non-test) code — Docker is only spun up by the backend test suite, and only when NODE_ENV=test. You don't need Docker running to develop or use the app this way. If you'd rather run the whole stack with no AWS at all — and run several isolated instances at once — see Dockerized Worktree Development.ds run ui
Spawns pnpm run dev inside frontend/, injecting VITE_* env vars (API/auth/media URLs, Pusher keys, Stripe public key, etc.) derived from your bound stage. The UI serves on http://localhost:8080.
Run both ds run backend and ds run ui together in separate terminals for the normal dev loop.
ds run backend on a personal stage failed its first rebuild with that error. It builds again on current develop — update your branch rather than working around it.On a machine that uses the checkout pool (main, wt1, wt2), you can run one leased checkout against this machine's own cloud stage instead of local Docker:
ds pool cloud wt1 # or: ds pool checkout wt1 --purpose <item> --mode cloud
ds pool cloud wt1 --off # back to local Docker
This is the same ds run backend and ds run ui as above, started for you in the background: the stage comes from the checkout's .env (the machine bundle's DEVSTRIDE_STAGE and DEVSTRIDE_DEV_PROFILE, written by ds secrets pull), the UI takes over the checkout's own frontend port, and the logs go to .ds/pool-cloud-backend.log and .ds/pool-cloud-ui.log in that checkout. It checks your AWS login first and never opens one, and it refuses the shared dev, staging and prod stages.
One sst dev per stage. Two Live Lambda sessions on the same stage take each other's requests, so the two starters refuse each other: ds pool cloud will not start while sst dev is already running on the machine, and a plain ds run backend from any other checkout refuses while a pool checkout is bound to the same stage. Only one pool checkout per machine can be cloud-bound at a time. ds pool cloud <checkout> --off (or ds pool checkin) stops both processes and deletes that stage's .ds/bind file in the checkout, so nothing run there later is silently bound to the cloud. The full flag list is in the Command Reference.
The pool lease belongs to the session that took it: a later ds pool command from another session, even one using the same name, is refused. From a plain terminal, run the export DEVSTRIDE_LEASE_TOKEN=… line ds pool checkout prints. To put an item's branch in the right place inside a leased checkout, use ds work start and ds work land.
A Neon database suspends when it sits idle — personal-stage databases do this routinely — so the first request after a quiet spell finds it waking. The backend, on every stage, waits for that instead of failing:
[db] connection attempt 1 failed (…); retrying — the database may be waking.An awake database connects in milliseconds, so nothing changes for a warm one. The same timeout also bounds waiting for a free connection when every pooled connection is busy, so a pool that stays saturated fails after about 21 seconds rather than waiting indefinitely.
DevStride's backend follows a strict CQRS layout per module (see API Development for the full Hono route → CommandBus/QueryBus call flow). Rather than hand-rolling the boilerplate, use ds g to scaffold a new command or query:
ds g command <Name> -m <module>
# alias: ds g c <Name> -m <module>
ds g query <Name> -m <module>
# alias: ds g q <Name> -m <module>
-m / --module is required — it's the target module directory (e.g. item, board, service-desk). Add -f / --force to overwrite an existing scaffold with the same name.
Running ds g command SendInvite -m user creates backend/src/modules/user/commands/send-invite/ with:
send-invite.command.ts — the Command class (props)send-invite.service.ts — the CommandHandlerBase implementation (your business logic goes here)send-invite.init.ts — registers the handler on the CommandBussend-invite.lambda.handler.ts — an HTTP entry point that constructs the command and executes it via CommandBusds g query scaffolds the equivalent layout under queries/<kebab-name>/ for QueryBus-backed reads.
*.lambda.handler.ts extends createZodHttpHandlerDto(...) against the raw APIGatewayProxyEvent/APIGatewayProxyStructuredResultV2 types — an older, now largely-abandoned pattern. The convention actually used across the codebase today (over 550 *.hono.handler.ts files, against about a dozen stragglers on the old pattern) is a *.hono.handler.ts file whose class extends HonoHandler<T extends RouteConfig> and defines its route — method, path, request/response schemas — inline inside the handler class itself; there's no separate Hono route file to "wire into." In practice, treat the generator's output as a props/service/init skeleton only, and hand-write the HTTP entry point as a *.hono.handler.ts extending HonoHandler<RouteConfig> following a neighboring handler in the same module as your template, rather than filling in the generated *.lambda.handler.ts as-is.ds migrations run
Runs the Drizzle SQL migrations against your bound stage's database, bringing it to the latest schema. This is what you run after pulling in someone else's schema changes, or after authoring your own with pnpm -C backend generate-sql.
ds migrations run applies both in order, and run-sql is the same thing under an older name. The deploy pipeline runs them as two separate steps, run-sql-expand before the code switch and run-sql-contract after it — see Deployment. Write every migration so it can be re-run (IF EXISTS / IF NOT EXISTS, additive first, backfill before a constraint); it runs in an auto-deploy pipeline with no one watching.The ds data commands operate on SQL tables in your bound stage's database. import, wipe, and copy are all blocked outright on prod. export is not blocked — there's no prod guard on it in source, so it will run (read-only) against a prod-configured stage if you point one at it.
ds data import <path> [-t/--tables <csv>]
Bulk-restores SQL tables from a previously exported directory (see Export below). Use -t to restore only specific tables (comma-separated table names); omit it to restore everything found at <path>.
ds data export [path] [-t/--tables <csv>]
Dumps SQL tables to disk. If you omit path, it defaults to .ds/data/<stage>/sql. Use -t to export only specific tables.
ds data wipe [-t/--tables <csv>]
Deletes rows from the selected tables (or all tables, if -t is omitted). Blocked on prod.
ds data copy [-o/--organization <id>] [-s/--source <envVarName|connString>]
Copies a single organization's data from a source database into your bound stage's database. -s accepts either the name of an env var holding a connection string (e.g. GOLDEN_DB_CONNECTION_STRING) or a full connection string directly; if omitted in an interactive terminal you'll be prompted to pick from the *_CONNECTION_STRING vars in .env, and non-interactively it falls back to SOURCE_DB_CONNECTION_STRING.
ds data copy is a generic, marker-unaware org copier. If the org id you pass belongs to the golden dataset (e.g. Acme), copying it this way lands dated data at the source's anchor date but leaves the stage's golden_handle.anchorIso marker stale — a later ds golden reanchor will compute its shift from the wrong baseline. The CLI will warn you if you target a golden org id; prefer ds golden import for seeding golden data onto a stage instead.# Local Docker stack — the instance registered for THIS checkout (`main` for the primary)
ds worktree cli <instance> script reset-db
# An AWS-bound personal stage (deliberate — name the stage first)
ds script reset-db
public schema, deletes every Cognito user in the target's user pool, re-runs all migrations, and builds the generated Golden dataset from nothing (Acme org + Cognito logins) — no source database connection is involved. On an AWS-bound stage it also re-adds Stripe products and sweeps orphan CloudFormation stacks; on the local Docker stack both are skipped (there is no Stripe account behind the placeholder key and nothing to sweep), and the §16 golden gate is skipped there by design. The golden stage guard refuses prod and the shared dev staging stage.ds script reset-db without the ds worktree cli <instance> prefix targets whatever dev stage the ds wrapper is bound to and drops its schema. On the local stack always route through the instance — and use the instance registered for the checkout you are on, because the CLI runs that directory's source (a secondary worktree naming main would reset the primary instance from the primary checkout's branch).If you add, remove, or change a Hono route's path, request/response shape, or query parameters, the frontend's typed API client will go stale. Regenerate it before relying on the frontend to see your change — see API Development for the ds script generate-api-client command and what it does.
ds golden commandsCommand Reference
Exhaustive, flag-by-flag reference for every real ds CLI command, verified directly against cli/commands/*.ts.
Dockerized Worktree Development
Run isolated, no-AWS DevStride instances at once — one per git worktree, each with its own database and test containers — and share a machine's fixed checkout pool (main, wt1, wt2) with leases, instant resets, client mode and cloud mode.