DB quickstart
This guide creates one notes table. The route inserts a note through Drizzle and reads it back through the same db handle.
The example uses the local SQLite default so you can verify the app before choosing Cloudflare D1 or a hosted libSQL database.
Set up @vitehub/db in this Vite app.
- Install @vitehub/db, drizzle-orm, h3, and vite
- Register hubDb()
- Add src/db/schema.ts with a notes table
- Add a Vite server entry that imports db and schema from @vitehub/db/drizzle
- Insert and list notes as JSON
Docs: /docs/vite/db/quickstart
Install DB
pnpm add @vitehub/db drizzle-orm h3 vite
@vitehub/db includes the libSQL client used by the local SQLite path. Add Drizzle Kit separately when you want to generate and manage migrations.
Register the integration
Register the Vite plugin. With no db.connection.url, ViteHub uses file:.data/db/sqlite.db in local Vite runtime.
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
import { hubDb } from '@vitehub/db/vite'
export default defineConfig({
appType: 'custom',
build: {
rollupOptions: {
input: resolve(import.meta.dirname, 'src/server.ts'),
},
},
plugins: [hubDb()],
})
Define the schema
Create the default Drizzle schema file:
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
export const notes = sqliteTable('notes', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
})
ViteHub discovers src/db/schema.ts and src/db/schema/* for the default database.
Use the database from a route
Add a Vite server entry that creates the table for the quickstart, inserts notes, and lists the newest notes first:
import { H3, readBody } from 'h3'
import { desc, sql } from 'drizzle-orm'
import { db, schema } from '@vitehub/db/drizzle'
const app = new H3()
async function ensureNotesTable() {
await db.run(sql`
create table if not exists notes (
id integer primary key autoincrement,
title text not null
)
`)
}
app.get('/api/notes', async () => {
await ensureNotesTable()
const notes = await db.select().from(schema.notes).orderBy(desc(schema.notes.id))
return { notes, ok: true }
})
app.post('/api/notes', async (event) => {
await ensureNotesTable()
const body = await readBody<{ title?: string }>(event)
const result = await db.insert(schema.notes).values({ title: body.title || 'hello database' }).returning()
return { note: result[0], ok: true }
})
export default app
db and schema are aliases for databases.default.db and databases.default.schema.
Verify the response
Run the app, then create a note:
curl -X POST http://localhost:3000/api/notes \
-H 'content-type: application/json' \
-d '{"title":"first note"}'
Read it back:
curl http://localhost:3000/api/notes
Expected response:
{
"notes": [
{
"id": 1,
"title": "first note"
}
],
"ok": true
}
Hosted providers
The route code stays the same when you switch providers. Change config and deployment environment only:
Next steps
- Use Usage for named databases, schema discovery, and migration directories.
- Use Runtime API for exact config shapes.
- Use Troubleshooting if schema discovery or hosted output fails.

