Schema

Define and manage your database schema with Drizzle ORM in Nuxt, including tables, columns, relations, and type-safe models.

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

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.