ViteHub is still experimental. Expect bugs and breaking changes.

Database

Define relational data with Drizzle and query it through generated ViteHub imports.

Use Database when your app needs relational schemas, constraints, joins, migrations, or queryable state. Define the schema with Drizzle, then query it through generated ViteHub imports.

Use KV for small values addressed by key, Blob for object storage, and Workspace for file-tree state.

Quick start

Install

Terminal
pnpm add @vite-hub/database drizzle-orm
pnpm add -D @vite-hub/cli drizzle-kit

Configure

vite.config.ts
import { hubDb } from '@vite-hub/database/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [hubDb()],
})

Start using it

src/database.ts
import { defineDatabase } from '@vite-hub/database'
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'

export default defineDatabase({
  schema: {
    notes: sqliteTable('notes', {
      id: integer('id').primaryKey(),
      title: text('title').notNull(),
    }),
  },
})

Public imports

ImportUse
defineDatabase from @vite-hub/databaseDeclare a Database Definition.
useDatabase from @vite-hub/database/drizzleSelect a generated Drizzle database and its schema by name.
hubDb from @vite-hub/database/viteRegister database discovery, generated schema, and Provider Output.
@vite-hub/database/configResolve database config values and discovery config.
@vite-hub/database/cliUse package-owned database CLI contribution.
@vite-hub/database/nuxtUse the narrow Nuxt D1 host-resource bridge.

All Database Definition, integration, connection, Cloudflare D1, Drizzle, and runtime config types are exported from @vite-hub/database.

Configure the Vite Integration

vite.config.ts
import { hubDb } from '@vite-hub/database/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [hubDb()],
})

The Vite config key is database.

OptionTypeDefaultDescription
databaseboolean or DBModulePublicOptionsdisabledEnables database discovery and generated runtime imports through vitehub() with true or an options object; false leaves it disabled.
database.projectRootstringeffective application rootSets the root used for Database Definition discovery, generated artifacts, and provisioning. Relative paths resolve from the Vite root in a Vite app and from the Nuxt rootDir in a Nuxt app.
database.cli.generatefalseenabledDisables package-owned schema generation CLI contribution.
database.cli.migratefalseenabledDisables package-owned migration CLI contribution.
database.connectionDatabaseConnectionConfiglocal SQLiteSupplies a hosted libSQL connection for Database Definitions that do not declare one. Definition connection values override matching integration values.
database.driverDatabaseRuntimeD1Options['driver']noneSelects Cloudflare D1 runtime output when configured at integration level. Value: d1.
database.bindingstringDB or DB_<NAME>Cloudflare D1 binding for integration-level runtime output.
database.databaseIdDatabaseConfigValueProvision StateCloudflare D1 database id.
database.previewDatabaseIdDatabaseConfigValuenoneCloudflare D1 preview database id.
database.databaseNameDatabaseConfigValuenoneCloudflare D1 database name.
database.migrationsTablestringprovider defaultCloudflare D1 migrations table.

Define a database

Database Definitions keep the Database Table Schema next to the server code that uses it.

src/database.ts
import { defineDatabase } from '@vite-hub/database'
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'

export default defineDatabase({
  schema: {
    notes: sqliteTable('notes', {
      id: integer('id').primaryKey(),
      title: text('title').notNull(),
      body: text('body').notNull(),
    }),
  },
})

A project uses either one Default Database or a set of Named Databases. Do not mix both modes in one app.

Database Definition options

defineDatabase() accepts one object.

OptionTypeRequiredDescription
namestringNamed Databases onlyRuntime identity. Must match the discovered file or directory name.
schemaRecord<string, Drizzle table>YesDatabase Table Schema source of truth.
connection.urlDatabaseConfigValueNoSQLite/libSQL connection URL. Defaults to .vitehub/data/database/sqlite.db for a Default Database.
connection.authTokenDatabaseConfigValueNoHosted database auth token.
cloudflare.bindingstringNoD1 binding. Defaults to DB for Default Database and DB_<NAME> for Named Databases.
cloudflare.databaseIdDatabaseConfigValueNoD1 database id.
cloudflare.httptrue | { url, authToken }NoExplicitly selects authenticated D1 raw HTTP access for local and hosted runtimes. true uses Cloudflare's API; an object selects a compatible proxy.
cloudflare.previewDatabaseIdDatabaseConfigValueNoD1 preview database id.
cloudflare.databaseNameDatabaseConfigValueNoD1 database name.
cloudflare.migrationsTablestringNoD1 migrations table.
drizzle.casingDrizzleCasingNoDrizzle casing option. Values: snake_case, camelCase.

ViteHub currently exposes sqlite as the public DatabaseDialect.

Generate and apply migrations

The Database integration adds migration commands to the ViteHub CLI. vite-hub includes the CLI; direct package installations need @vite-hub/cli as shown in the quick start. Run the commands from the project root:

Terminal
pnpm vitehub db generate
pnpm vitehub db migrate

db generate refreshes the generated Drizzle config and creates migrations from your Database Definitions. Pass --name <name> to name a migration or --custom to create an empty migration. db migrate refreshes the config and applies pending migrations.

Use it at runtime

Call useDatabase() from server code with the discovered database name. Use default for a Default Database.

server/api/notes.get.ts
import { useDatabase } from '@vite-hub/database/drizzle'

export default defineEventHandler(() => {
  const { db, schema } = useDatabase('default')
  return db.select().from(schema.notes)
})

The @vite-hub/database/drizzle runtime import is resolved by the ViteHub Vite Integration for server code and provider output. Do not run files that import it directly with plain node; run them through your Vite-built server path or provider output.

Use Named Databases when the app needs more than one independent database.

src/analytics.database.ts
import { defineDatabase } from '@vite-hub/database'
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'

const events = sqliteTable('events', {
  id: integer('id').primaryKey(),
  name: text('name').notNull(),
})

export default defineDatabase({
  name: 'analytics',
  schema: { events },
})

Name a database for a real data or deployment split, not for a source-code folder.

server/api/events.get.ts
import { useDatabase } from '@vite-hub/database/drizzle'

export default defineEventHandler(() => {
  const { db, schema } = useDatabase('analytics')
  return db.select().from(schema.events)
})

Providers

Provider/runtimeConfigure withNuance
Local SQLiteconnection.url or no connection configDefault for local development and generated Drizzle artifacts.
Hosted SQLite/libSQL-style connectionconnection.url and optional connection.authTokenKeep URLs and tokens in Server Env when they are secrets.
Cloudflare D1cloudflare Definition options or integration-level database.driver: 'd1'Uses a D1 binding on Cloudflare. Local development and hosted Vercel output use D1 only when cloudflare.http is selected explicitly.

Use Cloudflare D1 over HTTP

A Database Definition can use the same D1 database during local development and from hosted providers. Cloudflare output prefers the configured binding. Set cloudflare.http: true to call Cloudflare's D1 raw API with CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN from Server Env.

server/databases/config.ts
import { defineDatabase } from '@vite-hub/database'

import { notes } from './schema'

export default defineDatabase({
  cloudflare: {
    databaseId: process.env.CLOUDFLARE_D1_DATABASE_ID,
    databaseName: process.env.CLOUDFLARE_D1_DATABASE_NAME,
    http: true,
  },
  schema: { notes },
})

Use cloudflare.http: { url, authToken } to send the same raw query wire format to an authenticated HTTP(S) proxy instead. Both values are required at runtime, and proxy authentication never falls back to CLOUDFLARE_API_TOKEN.

server/databases/config.ts
import { defineDatabase } from '@vite-hub/database'

import { notes } from './schema'

export default defineDatabase({
  cloudflare: {
    databaseId: process.env.CLOUDFLARE_D1_DATABASE_ID,
    http: {
      authToken: process.env.D1_HTTP_TOKEN,
      url: process.env.D1_HTTP_URL,
    },
  },
  schema: { notes },
})

Selecting D1 HTTP also generates Drizzle Kit's d1-http credentials. Migration and inspection commands use Cloudflare's API with CLOUDFLARE_ACCOUNT_ID, the database id, and CLOUDFLARE_API_TOKEN; those credentials are never embedded in generated output.

Cloudflare describes its built-in D1 REST API as best suited to administrative use because the global Cloudflare API rate limit applies. For sustained application traffic, use a narrowly authenticated proxy Worker and validate which queries or tables it may access. See Cloudflare's D1 proxy Worker guide.

Omitting cloudflare.http preserves local SQLite and the existing hosted libSQL selection even when cloudflare.databaseId is present.

Select a hosted database for Vercel

Keep the Database Definition limited to tables, then select its hosted libSQL connection in the Vite Integration. Runtime Env declarations preserve the Vercel Marketplace environment variable lookup in generated output instead of embedding credentials at build time.

vite.config.ts
import { hubDb } from '@vite-hub/database/vite'
import { env, hubEnv } from '@vite-hub/env/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    hubEnv(),
    hubDb({
      connection: {
        url: env({ source: env.source('TURSO_DATABASE_URL') }),
        authToken: env({ secret: true, source: env.source('TURSO_AUTH_TOKEN') }),
      },
    }),
  ],
})

This connection is a deployment default. A Database Definition can still declare a different connection.url or connection.authToken; those values take precedence for that database.

Provider output

The Database Package discovers Database Definitions, generates the Drizzle Runtime Surface, produces generated schema artifacts, and wires provider-specific output. Provider bindings are integration details; the public database identity is the Default Database or Named Database.

Put Cloudflare D1 bindings, hosted libSQL URLs, and Nuxt host resources in Database configuration or host setup. Route code keeps using the generated Drizzle imports.

Cloudflare Nuxt output copies discovered D1 migration SQL beside the generated Wrangler config, so .output/server can apply migrations without the source checkout.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@vite-hub/database/nuxt'],
  database: {
    driver: 'd1',
    databaseName: 'app-content',
  },
})

Run vitehub provision run --provider cloudflare to resolve the D1 id into .vitehub/provision.json. Cloudflare production builds fail before deployment when provision state, database.databaseId, and an existing complete matching Nitro Wrangler binding all fail to supply the id.

@vite-hub/database/nuxt is a narrow Nuxt lifecycle bridge for one D1 Database Host Resource, mainly to keep Nuxt Content and Cloudflare wrangler.d1_databases in sync. Discovered Database Definitions still own the Drizzle Runtime Surface.

Connect it to Agents

Direct Database access is for server code. To let a model inspect schema or run guarded statements, attach the Database Capability.

The Database Capability is not a raw Drizzle client proxy. It uses agent-facing guardrails such as schema mode, data mode, write approvals, and the single-statement SQL guardrail. Read Official capabilities before exposing database access to an Agent.

Production checks

Database Table Schema is the schema in code. The live schema can differ when migrations haven't run or when an Agent has schema write permission.

Keep provider credentials in Server Env. Direct D1 HTTP access sends CLOUDFLARE_API_TOKEN only as the Cloudflare Bearer credential; proxy access sends only its configured cloudflare.http.authToken. Keep migrations, backup behavior, and hosted database lifecycle in deployment workflows instead of hiding them in route code.

Next steps