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 database property to your desired SQL dialect:

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

Database schema

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

server/database/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(),
})
Database schema can be defined in a single file or in multiple files, these files are scanned and automatically imported for each Nuxt layer:
  • server/database/schema.ts
  • server/database/schema/*.ts
NuxtHub support filtering files by dialect by using the .{dialect}.ts suffix, e.g. server/database/schema.postgresql.ts will be only imported if hub.database is postgresql.

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

import * as schema from 'hub:database:schema'
// or
import { schema } from 'hub:database'
If you are a Nuxt module developer, you can also extend the database schema by using the hub:database: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:database:schema:extend', async ({ dialect, paths }) => {
      // Add your module drizzle schema files for the given dialect
      // e.g. ./schema/pages.postgresql.ts if hub.database is 'postgresql'
      paths.push(await resolvePath(`./schema/pages.${dialect}`))
    })
  }
})
Learn more about Drizzle ORM schema on the Drizzle documentation.

Generate migrations

Generate the database migrations from your schema:

Terminal
npx nuxthub database generate

This creates SQL migration files in server/database/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:database.
Make sure to run npx nuxthub database generate to generate the database migrations each time you change your database schema and restart the development server.

Query database

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

SQL Select

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

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:database'

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:database'

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:database'

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/database/migrations for migrations in each Nuxt layer.

To scan additional directories, specify them in your config:

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

For more control (e.g., in Nuxt modules), use the hub:database: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:database:migrations:dirs', (dirs) => {
      dirs.push(resolve('./db-migrations'))
    })
  }
})
All migration files are copied to .data/database/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 nuxthub database generate

This will generate new migrations files in server/database/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 nuxthub database 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:database: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:database:queries:paths', (paths, dialect) => {
      paths.push(resolve(`./db-queries/seed-admin.${dialect}.sql`))
    })
  }
})
All migrations queries are copied to .data/database/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
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 useDrizzle().insert(tables.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.

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 PGlite.

Advanced configuration

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

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

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 now accepts a SQL dialect instead of a boolean to enable it.

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

Valid dialects are sqlite, postgresql and mysql.

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 db = useDrizzle()
const result = await db.select().from(tables.users)

Migration files

Your existing SQL migration files continue to work! No changes needed.