SQL Database

Access a SQL database with Drizzle ORM in Nuxt to store and retrieve relational data with full type-safety.

NuxtHub Database provides a type-safe SQL database powered by Drizzle ORM, supporting PostgreSQL, MySQL, and SQLite with smart detection and automatic migrations at build time.

Getting started

Install dependencies

Install Drizzle ORM, Drizzle Kit, and the appropriate driver(s) for the database you are using:

pnpm add drizzle-orm drizzle-kit postgres @electric-sql/pglite
NuxtHub automatically detects your database connection using environment variables:
  • Uses PGlite (embedded PostgreSQL) if no environment variables are set.
  • Uses postgres-js driver if you set DATABASE_URL, POSTGRES_URL, or POSTGRESQL_URL environment variable.

Set SQL dialect

Enable the database in your nuxt.config.ts by setting the db property to your desired SQL dialect:

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
    db: 'postgresql'
  }
})

Database schema

Create your database schema with full TypeScript support using Drizzle ORM:

server/db/schema.ts
import { pgTable, text, serial, timestamp } from 'drizzle-orm/pg-core'

export const users = pgTable('users', {
  id: serial().primaryKey(),
  name: text().notNull(),
  email: text().notNull().unique(),
  password: text().notNull(),
  avatar: text().notNull(),
  createdAt: timestamp().notNull().defaultNow(),
})
Learn more about defining the database schema files.

Generate migrations

Generate the database migrations from your schema:

Terminal
npx nuxt db generate

This creates SQL migration files in server/db/migrations/{dialect}/ which are automatically applied during deployment and development.

That's it! You can now start your development server and query your database using the db instance from hub:db.
Make sure to run npx nuxt db generate to generate the database migrations each time you change your database schema and restart the development server.

Schema definition

NuxtHub supports defining the database schema in multiple files and directories, allowing you to organize your schema files in a way that makes sense for your project, but also open the possibility to Nuxt modules to extend the database schema.

Schema files

Database schema can be defined in a single file or in multiple files, these files are scanned and automatically imported following this glob pattern:

  • server/db/schema.ts
  • server/db/schema.{dialect}.ts
  • server/db/schema/*.ts
  • server/db/schema/*.{dialect}.ts

The merged schema is exported in hub:db:schema or via the schema object in the hub:db namespace:

import * as schema from 'hub:db:schema'
// or
import { schema } from 'hub:db'
You can locate the generated schema file at .nuxt/hub/db/schema.mjs.
Learn more about Drizzle ORM schema on the Drizzle documentation.

Nuxt layers

Database schema is scanned and automatically imported for each Nuxt layer.

This meands that you can also define schema in the layers directory:

Directory structure
layers/cms/server/db/schema.ts
layers/products/server/db/schema/products.ts

Nuxt modules

If you are a Nuxt module developer, you can also extend the database schema by using the hub:db:schema:extend hook:

modules/cms/index.ts
import { defineNuxtModule, createResolver } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const { resolvePath } = createResolver(import.meta.url)
    nuxt.hook('hub:db:schema:extend', async ({ dialect, paths }) => {
      // Add your module drizzle schema files for the given dialect
      // e.g. ./schema/pages.postgresql.ts if hub.db is 'postgresql'
      paths.push(await resolvePath(`./schema/pages.${dialect}`))
    })
  }
})

Sharing types with Vue

Types inferred from your database schema are only available on the server-side by default. To share these types with your Vue application, you can use the shared/ directory which is auto-imported across both server and client.

Create a types file in the shared/types/ directory:

shared/types/db.ts
import { users, posts } from 'hub:db:schema'

// Select types (for reading data)
export type User = typeof users.$inferSelect
export type Post = typeof posts.$inferSelect

// Insert types (for creating data)
export type NewUser = typeof users.$inferInsert
export type NewPost = typeof posts.$inferInsert

These types are now auto-imported and available in your Vue components, composables, and API routes:

<script setup lang="ts">
const { data: users } = await useFetch<User[]>('/api/users')
</script>
You can also create more specific types by using Pick and Omit TypeScript's built-in utility types.
shared/types/db.ts
// User without password for public API responses
export type PublicUser = Omit<User, 'password'>

// Only the fields needed for user creation form
export type UserForm = Pick<NewUser, 'name' | 'email' | 'password'>

Query database

Now that you have your database schema and migrations set up, you can start querying your database.

The hub:db module provides access to the database through a Drizzle ORM instance.

import { db } from 'hub:db'
db is auto-imported on server-side, you can directly use it without importing it from hub:db.

SQL Select

server/api/users.get.ts
import { db, schema } from 'hub:db'

export default eventHandler(async (event) => {
  return await db.query.users.findMany()
  // or
  return await db.select().from(schema.users)
})
Learn more about Drizzle ORM select on the Drizzle documentation.

SQL Insert

server/api/users.post.ts
import { db, schema } from 'hub:db'

export default eventHandler(async (event) => {
  const { name, email } = await readBody(event)

  return await db
    .insert(schema.users)
    .values({
      name,
      email,
      createdAt: new Date()
    })
    .returning()
})
Learn more about Drizzle ORM insert on the Drizzle documentation.

SQL Update

server/api/users/[id].patch.ts
import { db, schema } from 'hub:db'

export default eventHandler(async (event) => {
  const { id } = getRouterParams(event)
  const { name } = await readBody(event)

  return await db
    .update(schema.users)
    .set({ name })
    .where(eq(tables.users.id, Number(id)))
    .returning()
})
Learn more about Drizzle ORM update on the Drizzle documentation.

SQL Delete

server/api/users/[id].delete.ts
import { db, schema } from 'hub:db'

export default eventHandler(async (event) => {
  const { id } = getRouterParams(event)

  const deletedUser = await db
    .delete(schema.users)
    .where(eq(schema.users.id, Number(id)))
    .returning()

  if (!deletedUser) {
    throw createError({
      statusCode: 404,
      message: 'User not found'
    })
  }

  return { deleted: true }
})
Learn more about Drizzle ORM delete on the Drizzle documentation.

Database migrations

Database migrations provide version control for your database schema. NuxtHub supports SQL migration files (.sql) and automatically applies them during development and deployment. Making them fully compatible with Drizzle Kit generated migrations.

Create dialect-specific migrations with .<dialect>.sql suffix (e.g., 0001_create-todos.postgresql.sql).

Migrations Directories

NuxtHub scans server/db/migrations for migrations in each Nuxt layer.

To scan additional directories, specify them in your config:

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
    db: {
      dialect: 'postgresql',
      migrationsDirs: [
        'server/db/custom-migrations/'
      ]
    }
  }
})

For more control (e.g., in Nuxt modules), use the hub:db:migrations:dirs hook:

import { createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    name: 'my-auth-module'
  },
  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    nuxt.hook('hub:db:migrations:dirs', (dirs) => {
      dirs.push(resolve('./db-migrations'))
    })
  }
})
All migration files are copied to .data/db/migrations when you run Nuxt, giving you a consolidated view.

Automatic migrations

Migrations are automatically applied when you:

  • Start the development server (npx nuxi dev)
  • Build the application (npx nuxi build)

Applied migrations are tracked in the _hub_migrations database table.

Generating migrations

Once you have updates your database schema, you can generate new migrations using the following command:

Terminal
npx nuxt db generate

This will generate new migrations files in server/db/migrations/{dialect}/ which are automatically applied during development and deployment.

Applying migrations

Once you have generated new migrations, you can apply them using the following command:

Terminal
npx nuxt db migrate

This will apply the new migrations to your database.

When running the development server, NuxtHub will automatically apply the migrations for you.

Post-migration queries

Advanced use case: These queries run after migrations but aren't tracked in _hub_migrations. Ensure they're idempotent.

Use the hub:db:queries:paths hook to run additional queries after migrations:

import { createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: {
    name: 'my-auth-module'
  },
  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    nuxt.hook('hub:db:queries:paths', (paths, dialect) => {
      paths.push(resolve(`./db-queries/seed-admin.${dialect}.sql`))
    })
  }
})
All migrations queries are copied to .data/db/queries when you run Nuxt, giving you a consolidated view.

Foreign-key constraints

For Cloudflare D1 with Drizzle ORM migrations, replace:

Example
-PRAGMA foreign_keys = OFF;
+PRAGMA defer_foreign_keys = on;

ALTER TABLE ...

-PRAGMA foreign_keys = ON;
+PRAGMA defer_foreign_keys = off;
Learn more about defer foreign key constraints in Cloudflare D1.

Database seed

You can populate your database with initial data using Nitro Tasks:

Enable Nitro tasks

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    experimental: {
      tasks: true
    }
  }
})

Create a seed task

server/tasks/seed.ts
import { db, schema } from 'hub:db'

export default defineTask({
  meta: {
    name: 'db:seed',
    description: 'Seed database with initial data'
  },
  async run() {
    console.log('Seeding database...')

    const users = [
      {
        name: 'John Doe',
        email: 'john@example.com',
        password: 'hashed_password',
        avatar: 'https://i.pravatar.cc/150?img=1',
        createdAt: new Date()
      },
      {
        name: 'Jane Doe',
        email: 'jane@example.com',
        password: 'hashed_password',
        avatar: 'https://i.pravatar.cc/150?img=2',
        createdAt: new Date()
      }
    ]

    await db.insert(schema.users).values(users)

    return { result: 'Database seeded successfully' }
  }
})

Execute the task

Open the Tasks tab in Nuxt DevTools and click on the db:seed task.

CLI

NuxtHub provides a CLI for managing your database migrations and running SQL queries accessible from the npx nuxt db command.

nuxt db generate

Generate database migrations from the schema.

Terminal
USAGE db generate [OPTIONS] 

OPTIONS
          --cwd    The directory to run the command in.
  -v, --verbose    Show verbose output.

nuxt db migrate

Apply database migrations to the database.

Terminal
USAGE db migrate [OPTIONS] 

OPTIONS

          --cwd    The directory to run the command in.                               
       --dotenv    Point to another .env file to load.
  -v, --verbose    Show verbose output.

nuxt db mark-as-migrated

Mark local database migration(s) as applied to the database.

Terminal
USAGE db mark-as-migrated [OPTIONS] [NAME]

ARGUMENTS
  NAME    The name of the migration to mark as applied.    

OPTIONS
          --cwd    The directory to run the command in.                               
       --dotenv    Point to another .env file to load.
  -v, --verbose    Show verbose output.

nuxt db drop

Drop a table from the database.

Terminal
USAGE db drop [OPTIONS] <TABLE>

ARGUMENTS
  TABLE    The name of the table to drop.    

OPTIONS
          --cwd    The directory to run the command in.                               
       --dotenv    Point to another .env file to load.
  -v, --verbose    Show verbose output.

nuxt db sql

Execute a SQL query against the database.

Terminal
USAGE db sql [OPTIONS] [QUERY]

ARGUMENTS
  QUERY    The SQL query to execute. If not provided, reads from stdin.    

OPTIONS
          --cwd    The directory to run the command in.                               
       --dotenv    Point to another .env file to load, relative to the root directory.
  -v, --verbose    Show verbose output.

Example usage:

Terminal
npx nuxt db sql "SELECT * FROM users"
# or
npx nuxt db sql < dump.sql

AI Agents

If you work with an IDE that supports AI agents, you can add the following text in your Agents.md or .cursor/rules file:

# Agent Instructions

/** ... your agent instructions ... */

## Database

- **Database Dialect**: The database dialect is set in the `nuxt.config.ts` file, within the `hub.db` option or `hub.db.dialect` property.
- **Drizzle Config**: Don't generate the `drizzle.config.ts` file manually, it is generated automatically by NuxtHub.
- **Generate Migrations**: Use `npx nuxt db generate` to automatically generate database migrations from schema changes
- **Never Write Manual Migrations**: Do not manually create SQL migration files in the `server/db/migrations/` directory
- **Workflow**:
  1. Create or modify the database schema in `server/db/schema.ts` or any other schema file in the `server/db/schema/` directory
  2. Run `npx nuxt db generate` to generate the migration
  3. Run `npx nuxt db migrate` to apply the migration to the database, or run `npx nuxt dev` to apply the migration during development
- **Access the database**: Use the `db` instance from `hub:db` to query the database, it is a Drizzle ORM instance.

Local development

During local development, view and edit your database from Nuxt DevTools using the Drizzle Studio:

At the moment, Drizzle Studio does not support SQLite.

Build-time Hooks

'hub:db:migrations:dirs'
(dirs: string[]) => void | Promise<void>
Add additional directories to scan for database migration files (.sql).
nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    'hub:db:migrations:dirs': (dirs) => {
      dirs.push('my-module/db/migrations')
    }
  }
})
'hub:db:queries:paths'
(queries: string[], dialect: string) => void | Promise<void>
Add queries that are not tracked in the _hub_migrations table which are applied after the database migrations complete.
nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    'hub:db:queries:paths': (queries, dialect) => {
      queries.push('my-module/db/queries')
    }
  }
})
'hub:db:schema:extend'
({ paths: string[], dialect: string }) => void | Promise<void>
Extend the database schema with additional files.
modules/my-module/index.ts
import { createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  setup(options, nuxt) {
    const { resolve } = createResolver(import.meta.url)

    nuxt.hook('hub:db:schema:extend', ({ paths, dialect }) => {
      paths.push(resolve(`./db/schema/pages.${dialect}.ts`))
    })
  }
})
Learn more about Nuxt server hooks on the Nuxt documentation.

Advanced configuration

For advanced use cases, you can explicitly configure the database connection:

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
    db: {
      dialect: 'postgresql',
      driver: 'postgres-js', // Optional: explicitly choose driver
      connection: {
        connectionString: process.env.DATABASE_URL
      }
    }
  }
})

Cloudflare D1 over HTTP

Use the d1-http driver to access a Cloudflare D1 database over HTTP. This is useful when you want to query your D1 database when hosting on other platforms.

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
    db: {
      dialect: 'sqlite',
      driver: 'd1-http'
    }
  }
})

This driver requires the following environment variables:

VariableDescription
NUXT_HUB_CLOUDFLARE_ACCOUNT_IDYour Cloudflare account ID
NUXT_HUB_CLOUDFLARE_API_TOKENA Cloudflare API token with D1 permissions
NUXT_HUB_CLOUDFLARE_DATABASE_IDThe ID of your D1 database
You can find your Cloudflare account ID and create API tokens in the Cloudflare dashboard. The API token needs D1:Edit permissions.

Migration guide

Breaking changes in NuxtHub v1: If you're upgrading from a previous version that used hubDatabase(), follow this migration guide.

Configuration changes

The database option has been renamed to db and now accepts a SQL dialect instead of a boolean.

nuxt.config.ts
export default defineNuxtConfig({
  hub: {
-    database: true
+    db: 'sqlite'
  }
})

Valid dialects are sqlite, postgresql and mysql.

Directory changes

The database directory has been renamed from server/database/ to server/db/:

- server/database/schema.ts
+ server/db/schema.ts

- server/database/migrations/
+ server/db/migrations/

Make sure to move your schema and migration files to the new location.

API changes

The old hubDatabase() function has been removed. You must now use Drizzle ORM.

Before:

const db = hubDatabase()
const result = await db.prepare('SELECT * FROM users').all()

After:

const result = await db.select().from(tables.users)

Migration files

Your existing SQL migration files continue to work! Just move them to server/db/migrations/.