Integrate into an Existing Worker

Use this when you have an existing Cloudflare Worker (typically a Hono app) and want to mount teenybase into it instead of running a separate backend folder. This is the advanced path. For most projects, creating a separate backend folder is simpler.

1. Install

bash
npm install teenybase hono
npm install -D wrangler @cloudflare/workers-types typescript

2. Scaffold with teeny init

bash
teeny init -t with-auth -y
bash
npx teeny init -t with-auth -y

teeny init creates the missing teenybase files without touching your existing files:

  • teenybase.ts
  • src/index.ts (only if it doesn't exist)
  • wrangler.jsonc
  • worker-configuration.d.ts
  • .dev.vars
  • migrations/

It also patches tsconfig.json so virtual:teenybase resolves correctly.

teeny init does not modify an existing package.json, which is why you install dependencies yourself in step 1.

3. Wire into your existing worker

If src/index.ts already exists, teeny init will not replace it. You need to add teenybase yourself.

Pass your existing Hono app as the second argument to teenyHono():

ts
import { Hono } from 'hono'
import {$Database, $Env, OpenApiExtension, PocketUIExtension, D1Adapter, teenyHono} from 'teenybase/worker'
import config from 'virtual:teenybase'

type Env = $Env & {Bindings: CloudflareBindings}

const app = new Hono<Env>()

// your existing routes
app.get('/healthz', (c) => c.json({ ok: true }))
app.get('/hello', (c) => c.text('Hello from my existing app'))

// add teenybase routes to your app
teenyHono<Env>(async (c) => {
  const db = new $Database(c, config, new D1Adapter(c.env.PRIMARY_DB))
  db.extensions.push(new OpenApiExtension(db, true))
  db.extensions.push(new PocketUIExtension(db))
  return db
}, app)

export default app

Your routes and teenybase's /api/v1/* routes coexist on the same app.

4. Add D1 binding

Add the D1 database binding to wrangler.jsonc:

jsonc
{
  "d1_databases": [
    {
      "binding": "PRIMARY_DB",
      "database_name": "<your-database-name>",
      "database_id": "<your-database-id>",
      "migrations_dir": "migrations"
    }
  ]
}

Regenerate types:

bash
npx wrangler types --env-interface CloudflareBindings

5. Run locally

bash
teeny deploy --local
teeny dev
bash
npx teeny deploy --local
npx teeny dev

Mounting under a sub-path

If you want teenybase at a custom prefix (e.g. /backend/api/v1/...), use Hono's app.route():

ts
const teenyApp = teenyHono<Env>(async (c) => {
  return new $Database(c, config, new D1Adapter(c.env.PRIMARY_DB))
})

const app = new Hono<Env>()
app.get('/', (c) => c.text('Home'))
app.route('/backend', teenyApp)  // teenybase at /backend/api/v1/...

export default app

File uploads (R2)

Add the R2 bucket binding to wrangler.jsonc:

jsonc
{
  "r2_buckets": [
    {
      "binding": "R2_BUCKET",
      "bucket_name": "my-app-files"
    }
  ]
}

Regenerate types:

bash
npx wrangler types --env-interface CloudflareBindings

Pass the bucket as the fourth argument to $Database:

ts
const db = new $Database(c, config, new D1Adapter(c.env.PRIMARY_DB), c.env.R2_BUCKET)

Deploy

bash
cp .dev.vars .prod.vars
bash
teeny deploy --remote
teeny status
bash
npx teeny deploy --remote
npx teeny status

Where code should live

  • teenybase.ts = schema, rules, auth, actions, email config
  • src/index.ts = worker wiring, extensions, custom Hono routes, R2 binding

Do not put app routes into teenybase.ts.