Getting Started with Teenybase
Your entire backend is defined in a single teenybase.ts config file -- tables, auth, rules, actions, everything. No backend code, no ORM, no route files. This guide walks you through setup and the core workflow.
Prerequisites: Node.js >= 18.14.1, teeny CLI already installed.
Before You Start
Before modifying teenybase.ts, understand what you are building. If you have not decided on your schema yet, describe what your backend needs to do -- "I need login", "I need a database for my recipes", "I need to process Stripe payments and track subscribers." The required tables, auth setup, and access rules can be inferred from that description.
Determine whether this is a new project or adding teenybase to an existing project. Infer from context (e.g., existing package.json or "add a backend to my app" means Option B). If unclear, decide before proceeding.
Non-Interactive Flags
Always pass these flags when running non-interactively. Without them, commands launch arrow-key prompts that hang when stdin is not a TTY.
| Flag | What it does |
|---|---|
-t, --template <template> | Template: with-auth (users + auth + rules) or blank (empty). |
-y, --yes | Skip all confirmation prompts, use defaults. |
Option A: New Project
teeny create my-app -t with-auth -y
cd my-appnpx teeny create my-app -t with-auth -y
cd my-appThis creates the directory, scaffolds all files, and runs npm install. The with-auth template includes a users table with email/password auth and row-level security. Use -t blank for an empty project.
Option B: Add to Existing Project
npm install teenybase hono
npm install -D wrangler @cloudflare/workers-types typescriptteeny init -ynpx teeny init -yteeny init detects existing files and only creates what's missing. It won't overwrite package.json or tsconfig.json (only patches in the "virtual:teenybase" path alias). Since it doesn't modify package.json, install deps yourself with the npm install lines above.
Key Files
| File | Purpose |
|---|---|
teenybase.ts | Backend config -- tables, fields, auth, rules, actions. The main file you edit. |
src/index.ts | Worker entry point. Usually unchanged unless you need custom routes or R2 storage. |
wrangler.jsonc | Cloudflare Workers config, D1/R2 bindings. |
.dev.vars | Local secrets (JWT_SECRET, JWT_SECRET_USERS, ADMIN_JWT_SECRET, ADMIN_SERVICE_TOKEN, POCKET_UI_VIEWER_PASSWORD, POCKET_UI_EDITOR_PASSWORD). |
.prod.vars | Production secrets (same keys, strong values). Not auto-created -- copy from .dev.vars. |
migrations/ | Auto-generated SQL. Don't edit manually. |
CLI Quick Reference
teeny create <name> [-t <tpl>] [-y] # Scaffold new project (runs npm install)
teeny init [-t <tpl>] [-y] # Add teenybase to existing project
teeny generate --local # Generate migrations from config changes
teeny deploy --local # Apply migrations to local database
teeny dev --local # Start local dev server (port 8787)
teeny deploy --remote # Deploy to Teenybase Cloud
teeny register # Create Teenybase Cloud account (free)
teeny login # Log in
teeny status # Show deployed URL and status
teeny secrets --remote --upload # Upload .prod.vars to production
teeny list # List deployed workers
teeny delete [name] # Delete a deployed worker
teeny logs # Stream production logs
teeny docs # List available documentation files
teeny skills # List available skills with descriptions
teeny inspect [--table <n>] [--validate] # Dump resolved DatabaseSettings as JSON
teeny --help # List commands and optionsnpx teeny create <name> [-t <tpl>] [-y] # Scaffold new project (runs npm install)
npx teeny init [-t <tpl>] [-y] # Add teenybase to existing project
npx teeny generate --local # Generate migrations from config changes
npx teeny deploy --local # Apply migrations to local database
npx teeny dev --local # Start local dev server (port 8787)
npx teeny deploy --remote # Deploy to Teenybase Cloud
npx teeny register # Create Teenybase Cloud account (free)
npx teeny login # Log in
npx teeny status # Show deployed URL and status
npx teeny secrets --remote --upload # Upload .prod.vars to production
npx teeny list # List deployed workers
npx teeny delete [name] # Delete a deployed worker
npx teeny logs # Stream production logs
npx teeny docs # List available documentation files
npx teeny skills # List available skills with descriptions
npx teeny inspect [--table <n>] [--validate] # Dump resolved DatabaseSettings as JSON
npx teeny --help # List commands and optionsHow It Works
Everything about your backend lives in one file: teenybase.ts (or teeny.config.ts -- all supported names). You edit this file, apply the changes, and your API updates automatically -- tables, auth, rules, docs, admin panel, everything.
┌─────────────────────────────┐
│ │
▼ │
teenybase.ts │
(define your backend) │
│ │
▼ │
teeny deploy --local --yes │
(generate SQL + apply locally) │
│ │
▼ │
teeny dev --local │
(start dev server) │
│ │
▼ │
test with curl / Swagger / PocketUI │
│ │
┌─────┴─────┐ │
│ │ │
▼ ▼ │
looks good? need changes? ─────────────┘
│
▼
teeny deploy --yes
(deploy to production)That's the whole loop. No build step, no ORM, no route files. Change the config, deploy, done.
Understanding the Config
teenybase.ts is your entire backend definition:
import { DatabaseSettings, TableAuthExtensionData,
TableRulesExtensionData } from 'teenybase'
import { baseFields, authFields, // ① Pre-built field sets
createdTrigger, updatedTrigger } from 'teenybase/scaffolds/fields'
export default {
appUrl: 'http://localhost:8787', // ② Used for OAuth redirects and email links
jwtSecret: '$JWT_SECRET', // ③ Secret from .dev.vars ($-prefixed = env var)
tables: [{
name: 'users', // ④ Table name → /api/v1/table/users/
autoSetUid: true, // ⑤ Auto-generate unique ID on insert
fields: [
...baseFields, // ⑥ id + created + updated
...authFields, // ⑦ username, email, email_verified, password, password_salt, name, avatar, role, meta
],
triggers: [createdTrigger, updatedTrigger], // ⑧ Auto-manage created/updated timestamps
extensions: [
{ name: 'auth', // ⑨ Enables sign-up, login, password reset, OAuth
jwtSecret: '$JWT_SECRET_USERS',
jwtTokenDuration: 3600,
maxTokenRefresh: 5,
} as TableAuthExtensionData,
{ name: 'rules', // ⑩ Row-level security — who can read/write what
listRule: 'auth.uid == id', // Only see your own record
viewRule: 'auth.uid == id',
createRule: 'true', // needed for sign-up (auth extension creates via insert)
updateRule: 'auth.uid == id',
deleteRule: 'auth.uid == id',
} as TableRulesExtensionData,
],
}],
} satisfies DatabaseSettings // ⑪ Type checking — IDE autocomplete for everythingKey concepts: $ prefix resolves env vars from .dev.vars / .prod.vars. Rules are expressions -- auth.uid == id becomes a SQL WHERE clause. Extensions add behavior (auth, rules, crud). Everything else is auto-generated: REST API, Swagger docs, admin panel.
Note: The default scaffold also includes authCookie: { name: 'teeny_auth' } and passwordConfirmSuffix: 'Confirm' on the auth extension. This enables browser Auth UI and requires passwordConfirm in sign-up/password-reset requests.
Local Development
After setup (Option A or B):
teeny generate --local # Generate migrations from your config
teeny deploy --local # Apply migrations to local SQLite database
teeny dev --local # Start dev server at http://localhost:8787npx teeny generate --local # Generate migrations from your config
npx teeny deploy --local # Apply migrations to local SQLite database
npx teeny dev --local # Start dev server at http://localhost:8787Available at: health check (/api/v1/health), Swagger UI (/api/v1/doc/ui), admin panel (/api/v1/pocket/).
Development Workflow
- Edit
teenybase.ts teeny generate --local+teeny deploy --local(generate and apply migrations)teeny dev --local(start server)- Test with curl / Swagger / PocketUI
- Iterate (back to step 1) or
teeny deploy --remote(production)
API Endpoint Reference
Base URL: http://localhost:8787/api/v1 (local). For deployed apps, run teeny status to get your production URL, then append /api/v1.
teeny statusnpx teeny statusCRUD:
POST /table/{table}/insert { "values": {...}, "returning": "*" }
GET|POST /table/{table}/select ?where=...&order=...&limit=...
GET|POST /table/{table}/list Same as select but returns { items, total }
GET /table/{table}/view/{id}
POST /table/{table}/update { "where": "id == '...'", "setValues": {...} }
POST /table/{table}/edit/{id} { "field": "value" }
POST /table/{table}/delete { "where": "id == '...'" }Auth:
POST /table/{table}/auth/sign-up { "username", "email", "password", "name" }
POST /table/{table}/auth/login-password { "identity", "password" }
POST /table/{table}/auth/refresh-token { "refresh_token" }
POST /table/{table}/auth/request-password-reset { "email" }
POST /table/{table}/auth/confirm-password-reset { "token", "password" }
POST /table/{table}/auth/request-verification Authorization: Bearer <token>
POST /table/{table}/auth/confirm-verification { "token" }
POST /table/{table}/auth/logout Authorization: Bearer <token>
POST /auth/logout Clears auth cookie onlyAuth header: Authorization: Bearer <token>
Other: GET /health, GET /doc/ui (Swagger), GET /doc (OpenAPI JSON)
Next Steps
- Configuration Reference -- every option in teenybase.ts
- Actions Guide -- server-side logic with typed parameters
- Connecting Your Frontend -- fetch examples, auth flow, CRUD
- Recipes & Patterns -- copy-paste examples for common use cases
- CLI Reference -- all commands with full options
- OAuth Guide -- set up Google, GitHub, Discord, or LinkedIn login
- API Endpoints -- full endpoint reference
- Adding to Existing Projects -- integrate teenybase into an existing Hono app
Technical Appendix
Testing the API with curl
# Sign up
curl -X POST http://localhost:8787/api/v1/table/users/auth/sign-up \
-H 'Content-Type: application/json' \
-d '{ "username": "testuser", "email": "test@example.com", "password": "mypassword", "name": "Test User" }'
# Response includes JWT `token` and `refresh_token`
# Login
curl -X POST http://localhost:8787/api/v1/table/users/auth/login-password \
-H 'Content-Type: application/json' \
-d '{ "identity": "test@example.com", "password": "mypassword" }'
# Query data (authenticated) — rules ensure you only see your own records
curl http://localhost:8787/api/v1/table/users/select \
-H 'Authorization: Bearer <token-from-login>'Adding a Second Table
Example: a posts table linked to users. Add sqlValue to your teenybase import, then add to your tables array:
{
name: 'posts',
autoSetUid: true,
fields: [
...baseFields,
{ name: 'author_id', type: 'relation', sqlType: 'text', notNull: true,
foreignKey: { table: 'users', column: 'id' } },
{ name: 'title', type: 'text', sqlType: 'text', notNull: true },
{ name: 'body', type: 'text', sqlType: 'text' },
{ name: 'published', type: 'bool', sqlType: 'boolean', default: sqlValue(false) },
],
triggers: [createdTrigger, updatedTrigger],
extensions: [
{ name: 'rules',
listRule: 'published == true | auth.uid == author_id',
viewRule: 'published == true | auth.uid == author_id',
createRule: 'auth.uid != null & author_id == auth.uid',
updateRule: 'auth.uid == author_id',
deleteRule: 'auth.uid == author_id',
} as TableRulesExtensionData,
],
},Then run:
teeny generate --local
teeny deploy --local
teeny dev --localnpx teeny generate --local
npx teeny deploy --local
npx teeny dev --localRow-Level Security (Rules)
The rules extension injects SQL WHERE clauses. Rules are expressions:
{ name: 'rules',
listRule: 'auth.uid == owner_id', viewRule: 'auth.uid == owner_id',
createRule: 'auth.uid != null', updateRule: 'auth.uid == owner_id',
deleteRule: 'auth.uid == owner_id',
} as TableRulesExtensionDataVariables: auth.uid (authenticated user's ID, null if not logged in), any column name, true/false (allow/deny all), null (deny all).
Operators: ==, !=, >, <, >=, <=, ~ (LIKE), !~ (NOT LIKE), in, @@ (FTS), & (AND), | (OR). Full syntax.
Environment Variables (Secrets)
$-prefixed values in teenybase.ts resolve from .dev.vars (local) or .prod.vars (production, uploaded via teeny secrets --remote --upload).
Default .dev.vars (generated by teeny create):
JWT_SECRET=dev-jwt-secret-change-in-production
JWT_SECRET_USERS=dev-users-jwt-secret-change-in-production
ADMIN_JWT_SECRET=dev-admin-jwt-secret-change-in-production
ADMIN_SERVICE_TOKEN=dev-admin-token
POCKET_UI_VIEWER_PASSWORD=viewer
POCKET_UI_EDITOR_PASSWORD=editorJWT_SECRET and JWT_SECRET_USERS are concatenated for user auth token signing -- use different values. See How JWT Signing Works. apiRoute is stored in infra.jsonc (auto-saved on deploy). Never commit .prod.vars.
Field Scaffolds
Import from teenybase/scaffolds/fields:
| Scaffold | Fields |
|---|---|
baseFields | id (text PK, auto-UID), created (auto-set), updated (auto-set) |
authFields | username, email, email_verified, password (hidden), password_salt (hidden), name, avatar (R2 file), role, meta (json) |
createdTrigger | Prevents updating created after insert |
updatedTrigger | Auto-updates updated on every update |
Extensions (OpenAPI & PocketUI)
Both are added in src/index.ts:
import { OpenApiExtension, PocketUIExtension } from 'teenybase/worker'
db.extensions.push(new OpenApiExtension(db)) // /api/v1/doc (JSON) + /api/v1/doc/ui (Swagger)
db.extensions.push(new PocketUIExtension(db)) // /api/v1/pocket/ (admin panel)OpenAPI: pass false to disable Swagger UI (new OpenApiExtension(db, false)). See config ref.
PocketUI: login with POCKET_UI_VIEWER_PASSWORD (read-only) or POCKET_UI_EDITOR_PASSWORD (read+write) from .dev.vars/.prod.vars. Also accepts ADMIN_SERVICE_TOKEN. Change defaults before production. See config ref.
Email, OAuth & More
Add email and authProviders to your config (alongside existing appUrl, jwtSecret, tables):
email: {
from: 'noreply@yourdomain.com',
mock: true, // logs to console in dev
variables: { company_name: 'My App', company_url: 'http://localhost:8787',
company_address: '', company_copyright: '© 2025 My App',
support_email: 'support@yourdomain.com' },
// Production: resend: { RESEND_API_KEY: '$RESEND_API_KEY' }
},
authProviders: [
{ name: 'google', clientId: '$GOOGLE_CLIENT_ID', clientSecret: '$GOOGLE_CLIENT_SECRET' },
],email enables verification + password reset endpoints. authProviders enables OAuth login. See OAuth Guide and Configuration Reference.
Deploy to Production (Teenybase Cloud)
teeny register # create account (one-time)
teeny deploy --remote --yes # deploynpx teeny register # create account (one-time)
npx teeny deploy --remote --yes # deployteeny status # see live URLnpx teeny status # see live URLOn first deploy, secrets are auto-generated (JWT_SECRET, JWT_SECRET_USERS, ADMIN_JWT_SECRET, ADMIN_SERVICE_TOKEN, POCKET_UI_VIEWER_PASSWORD, POCKET_UI_EDITOR_PASSWORD) and saved to .prod.vars.
Custom domain: add "routes": [{ "pattern": "api.myapp.com", "custom_domain": true }] to wrangler.jsonc, then run teeny deploy --remote --yes. The domain must have DNS on Cloudflare. Update appUrl in teenybase.ts to match.