Database seeders

Database seeders

Database seeding is a way to set up your application with some initial data required to run and use the application. For example:

  • Creating a seeder to insert countries, states, and cities before deploying and running your application.
  • Or a seeder to insert users inside the database for local development.

The seeders are stored inside the database/seeders directory. You can create a new seeder file by running the following Ace command.

node ace make:seeder User
# CREATE: database/seeders/user_seeder.ts

Every seeder file must extend the BaseSeeder class and implement the run method.

The following example uses a Lucid model to create multiple users. However, you can also use the Database query builder directly. In other words, seeders don't care what you write inside the run method.

database/seeders/user.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import User from '#models/user'
export default class UserSeeder extends BaseSeeder {
async run() {
await User.createMany([
{
email: 'virk@adonisjs.com',
password: 'secret',
},
{
email: 'romain@adonisjs.com',
password: 'supersecret',
},
])
}
}

Running seeders

You can execute all or selected database seeders by running the following Ace command.

# runs all
node ace db:seed

You can define the --files flag multiple times to run more than one file. Also, you will have to define the complete path to the seeder file. We opted for the complete path because your terminal shell can autocomplete the path for you.

node ace db:seed --files "./database/seeders/user_seeder.ts"

You can also select the seeder files interactively by running the db:seed command in interactive mode.

node ace db:seed -i

Environment specific seeders

Lucid allows you to mark a seeder file to run only in a specific environment by changing the environment property. This ensures you don't seed your production, testing, or development database with data you don't want by mistake.

The seeders using the environment flag will only run when the NODE_ENV environment variable is set to their respective value.

import { BaseSeeder } from '@adonisjs/lucid/seeders'
export default class UserSeeder extends BaseSeeder {
static environment = ['development', 'testing']
async run() {}
}

Idempotent operations

Unlike migrations, there is no tracking system in place for the database seeders. In other words, executing a seeder multiple times will perform the inserts multiple times as well.

Based upon the nature of a seeder, you may or may not want this behavior. For example:

  • It is okay to run a PostSeeder multiple times and increase the number of posts you have in the database.
  • On the other hand, you would want the CountrySeeder to perform inserts only once. These kinds of seeders are idempotent.

Fortunately, Lucid models have inbuilt support for idempotent operations using updateOrCreate or fetchOrCreateMany. Continuing with the CountrySeeder, the following is an example of creating countries only once.

import { BaseSeeder } from '@adonisjs/lucid/seeders'
import Country from '#models/country'
export default class CountrySeeder extends BaseSeeder {
async run() {
const uniqueKey = 'isoCode'
await Country.updateOrCreateMany(uniqueKey, [
{
isoCode: 'IN',
name: 'India',
},
{
isoCode: 'FR',
name: 'France',
},
{
isoCode: 'TH',
name: ' Thailand',
},
])
}
}

In the above example, the updateOrCreateMany method will look for existing rows inside the database using the isoCode code and only inserts the missing ones and hence running the CountrySeeder for multiple times will not insert duplicate rows.

Customizing database connection

The db:seed command accepts an optional --connection flag and forwards it to the seeder files as a connection property. From there on, you can use this property to set the appropriate connection during your model interactions. For example:

import { BaseSeeder } from '@adonisjs/lucid/seeders'
import User from '#models/user'
export default class UserSeeder extends BaseSeeder {
async run() {
await User.create(
{
email: 'virk@adonisjs.com',
password: 'secret',
},
{
connection: this.connection, // 👈
}
)
}
}

Now you can specify the --connection flag on your db:seed command, and the UserSeeder will use it.

node ace db:seed --connection=tenant-1

Seeders config

The configuration for seeders is stored inside the config/database.ts file under the connection config object.

paths

Define the paths for loading the database seeder files. You can also define a path to an installed package.

{
mysql: {
client: 'mysql2',
seeders: {
paths: ['./database/seeders', '@somepackage/seeders-dir']
}
}
}

Customizing seeders order

The db:seed command runs all the seeders in the order they are stored on the filesystem.

If you want certain seeders to run before the other seeders, then either you can prefix a counter to file names or you can create a Main seeder directory as follows.

Step 1. Create the main seeder

Create the main seeder file by running the following Ace command.

node ace make:seeder main/index
# CREATE: database/seeders/main/index_seeder.ts

Step 2. Register its path inside the seeders config

Open the config/database.ts file and register the path to the main directory inside the connection config.

After the following change, the db:seed command will scan the ./database/seeders/main directory.

{
mysql: {
client: 'mysql2',
// ... rest of the config
seeders: {
paths: ['./database/seeders/main']
}
}
}

Step 3. Import other seeders inside the main seeder

Now, you can manually import all the seeders inside the index_seeder file and execute them in any order you want.

Following is an example implementation of the Main seeder. Feel free to customize it as per your requirements.

import { BaseSeeder } from '@adonisjs/lucid/seeders'
import app from '@adonisjs/core/services/app'
export default class IndexSeeder extends BaseSeeder {
private async seed(Seeder: { default: typeof BaseSeeder }) {
/**
* Do not run when not in a environment specified in Seeder
*/
if (
!Seeder.default.environment ||
(!Seeder.default.environment.includes('development') && app.inDev) ||
(!Seeder.default.environment.includes('testing') && app.inTest) ||
(!Seeder.default.environment.includes('production') && app.inProduction)
) {
return
}
await new Seeder.default(this.client).run()
}
async run() {
await this.seed(await import('#database/seeders/category'))
await this.seed(await import('#database/seeders/user'))
await this.seed(await import('#database/seeders/post'))
}
}

Step 4. Run the db:seed command

node ace db:seed
# completed database/seeders/main/index_seeder