Drizzle ORM provides type-safe database operations with minimal boilerplate. It’s perfect for interacting with an SQLite database with Nuxt. Nitro tasks provide the icing on the cake making it easy to run seeders directly from the Nuxt devtools. In this article, I’ll show you how to get setup with drizzle ORM in your Nuxt project and you’ll be migrating and seeding your database in no time!
Install Drizzle ORM for database operations and Drizzle Kit for migration tools.
npm i drizzle-orm @libsql/client dotenv
npm i -D drizzle-kit tsx
Then create a .env
variable to configure where the SQLite database file will live. Let’s add it to a folder called .data
as that’s already in a Nuxt project’s .gitignore
by default.
DB_FILE_NAME=file:.data/local.db
Finally, create the database file.
mkdir ./.data && touch ./.data/local.db
The drizzle config file is used by Drizzle Kit and contains all the information about your database connection, migration folder, and schema files. Set it up as follows:
// drizzle.config.ts
import 'dotenv/config';
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
out: './drizzle',
// a file defining our data structure we'll create in a minute
schema: './server/db/schema.ts',
// the type of database we're using
dialect: 'sqlite',
// the file where our database will be stored
dbCredentials: {
url: process.env.DB_FILE_NAME!,
},
});
Drizzle Kit will be used during the migration process to connect to the database. You’ll also need to connect to the database when running the Nuxt application and the Nitro tasks. Create a utility function to accomplish this within the server/utils
directory.
// @/server/utils/db.ts
import { drizzle } from 'drizzle-orm/libsql';
export function useDb() {
return drizzle(import.meta.env.DB_FILE_NAME!);
}
With setup now complete you can start modeling what data will look like. Define your database structure using Drizzle's type-safe schema builder. This example creates a users table that stores basic user data. Notice the filename matches up with the schema
option we set in drizzle.config.ts
in the previous step.
// server/db/schema.ts
import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const usersTable = sqliteTable("users", {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
age: int().notNull(),
email: text().notNull().unique(),
});
Migrations help you version control database changes. Add these scripts to manage them.
{
"scripts": {
// Creates migration files from schema changes
"db:generate": "drizzle-kit generate",
// Applies migrations to database
"db:migrate": "drizzle-kit migrate",
// Directly apply changes to your database
// convenient method for quickly testing new schema designs in dev
"db:push": "drizzle-kit push"
}
}
Now we can apply our database schema to the database in development with:
npm run db:push
Follow these steps when making changes to your database structure.
For quick iteration during development:
schema.ts
npm run db:push
For scalable database management for prod with a record of db migrations stored in the git repo:
schema.ts
npm run db:generate
- Creates SQL migration filesnpm run db:migrate
- Executes changes on databaseWith the database structure now in place, you can seed it with dummy data. This helps you develop your application with realistic data in place with very little effort. This is a great use-case for a Nitro tasks and Faker.js!
First install Faker.js for easy dummy data generation.
npm install @faker-js/faker --save-dev
Then bootstrap a new nitro task in server/tasks/db/seed.ts
// server/tasks/seed.ts
export default defineTask({
// give the task a name and a description
meta: {
name: 'db:seed',
description: 'Seed database'
},
async run() {
// provide the logic to seed the database
}
})
Within the task run
function, you can generate as many fake users as you’d like with faker.
// server/tasks/seed.ts
import { usersTable } from "../db/schema"
import { faker } from '@faker-js/faker';
export default defineTask({
meta: {
name: 'db:seed',
description: 'Seed database'
},
async run() {
// initialize an array of users
// whose type is based on the structure of
// the users table
const users: typeof usersTable.$inferInsert[] = [];
// push 1000 fake users generated by faker.js into the array
for (let i = 0; i < 1000; i++) {
users.push({
name: faker.person.firstName(),
email: faker.internet.email(),
age: faker.number.int({ min: 3, max: 75 })
});
}
}
})
Then insert those users into the database with the useDb
utility function and return a success message.
// server/tasks/seed.ts
import { useDb } from "../utils/db";
import { usersTable } from "../db/schema"
import { faker } from '@faker-js/faker';
export default defineTask({
meta: {
name: 'db:seed',
description: 'Seed database'
},
async run() {
// generate users ...
// use the db utility function to insert the fake users
const db = useDb();
await db.insert(usersTable).values(users) // important to await!
// return result
return {
result: 'success',
message: `${users.length} users seeded`
}
}
})
Finally wrap the full thing in a try catch to handle errors.
// server/tasks/seed.ts
import { useDb } from "../utils/db";
import { usersTable } from "../db/schema"
import { faker } from '@faker-js/faker';
export default defineTask({
meta: {
name: 'db:seed',
description: 'Seed database'
},
async run() {
try {
// the full seed logic here...
return {
result: 'success',
message: `${users.length} users seeded`
}
// handle errors
} catch (err) {
if (err instanceof Error) {
return {
error: err.message
};
} else {
return {
error: 'Unknown error'
}
}
}
}
})
The full seed script looks like this:
// server/tasks/seed.ts
import { useDb } from "../utils/db";
import { usersTable } from "../db/schema"
import { faker } from '@faker-js/faker';
export default defineTask({
meta: {
name: 'db:seed',
description: 'Seed database'
},
async run() {
try {
// initialize an array of users
// whose type is based on the structure of
// the users table
const users: typeof usersTable.$inferInsert[] = [];
// push 1000 fake users generated by faker.js into the array
for (let i = 0; i < 1000; i++) {
users.push({
name: faker.person.firstName(),
email: faker.internet.email(),
age: faker.number.int({ min: 3, max: 75 })
});
}
// use the db utility function to insert the fake users
const db = useDb();
await db.insert(usersTable).values(users) // important to await!
// return result
return {
result: 'success',
message: `${users.length} users seeded`
}
} catch (err) {
if (err instanceof Error) {
return {
error: err.message
};
} else {
return {
error: 'Unknown error'
}
}
}
}
})
Finally, in order for tasks to work, you must enable the feature in nuxt.config.ts
with the nitro.experimental.tasks
option.
// nuxt.config.ts
export default defineNuxtConfig({
// ...
nitro: {
experimental: {
tasks: true
}
}
})
Now the seed is setup and ready to run. You can run the seed anytime you want from the Nuxt devtools. Look for the devtools’ item with the “play” looking icon (it might be under the vertical ellipsis) on the left hand side of the devtools and press the send button to execute your task!
That’s it! 🎉 You can now easily seed new users anytime you want.
Now that you have some data in the database, it would be nice to view it. No problem! Setup a script in package.json
to run drizzle studio.
"scripts": {
"db:studio": "drizzle-kit studio"
},
And run it to open up a visual database client in your browser.
npm run db:studio
In this article we talked about how to use Nuxt, Nitro, and Drizzle ORM for easily managing common database needs like migration and seeding. If you’d like to get more practice, tips, and tricks using Faker.js to generate fake data for seeding a db, checkout our course Generating Fake Data with Faker.js. If you’d like to dive deeper into developing full stack apps with Nuxt checkout the course Nuxt.js 3 Fundamentals or Mastering Nuxt.
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.