How to seed database using TypeORM in a NestJS project
Complete database seeding configuration from scratch in a NestJS project using TypeORM
Motivation
It is often required to seed a database with initial data during development. In a NestJS + TypeORM project, we can do that with the help of typeorm-extension.
Initial project setup
Let's initialize our project using Nest CLI.
nest new nest-typeorm
Navigate to the project folder and install the necessary dependencies. In this example, I am using PostgreSQL, therefore I have added pg
as a dependency.
cd nest-typeorm
npm install --save @nestjs/typeorm typeorm pg
Database connection
Let's create a separate Nest module for handling database-related tasks.
nest g module db
Create the data-source.ts
file inside the src/db
directory. This file will contain database connection configurations.
// src/db/data-source.ts
import { config } from 'dotenv';
import { DataSourceOptions } from 'typeorm';
config();
export const dataSourceOptions: DataSourceOptions = {
type: 'postgres',
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: ['dist/resources/**/*.entity.js'],
synchronize: true, // do not set it true in production application
};
Setting
synchronize: true
shouldn't be used in production - otherwise you can lose production data. You should use migration instead. I have an article on How to setup TypeORM migrations in a NestJS project.
Use this dataSourceOptions
for setting up TypeOrmModule inside the DbModule that we generated using Nest CLI.
// src/db/db.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { dataSourceOptions } from './data-source';
@Module({
imports: [TypeOrmModule.forRoot(dataSourceOptions)],
})
export class DbModule {}
Now if you run the project using npm run start:dev
command, it should be able to connect to the database. However, you have to make sure the database server is up and running and the database with the name that we specified in the data-source
file is already created.
Generate an entity
Generate users
resource using the CLI.
nest g resource resource/users
It will generate the users
module, controller, service, DTOs and entity file inside src/resource/users
directory.
Let's update the user.entity.ts
file and add some columns there.
// src/resources/users/entities/user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'first_name' })
firstName: string;
@Column({ name: 'last_name' })
lastName: string;
}
Since we set synchronize: true
in our datasource options, if we run this project using npm start
command, it should create the user
table in our database.
The database should already be running in the specified host and port before running the app.
If the user
table is created successfully then we can start working on the seeding part.
Seeding
We are going to use typeorm-extension for seeding our database. Let's install it first.
npm install typeorm-extension --save
Now we need a seeder class for seeding the user
entity. Seeder
interface is exported from typeorm-extension
, we just need to implement it. Let's create the class in src/db/seeds/user.seeder.ts
file.
// src/db/seeds/user.seeder.ts
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { User } from '../../resources/users/entities/user.entity';
export default class UserSeeder implements Seeder {
public async run(
dataSource: DataSource,
factoryManager: SeederFactoryManager,
): Promise<void> {
await dataSource.query('TRUNCATE "user" RESTART IDENTITY;');
const repository = dataSource.getRepository(User);
await repository.insert({
firstName: 'Mazedul',
lastName: 'Islam',
});
}
}
We have implemented the run
method from the Seeder
interface. When we execute the seed command using the typeorm-extension
CLI, this run function will be executed. The run
method has access to the DataSource
and SeederFactoryManager
instances, as they are passed as arguments.
At first, we truncate the user
table and restart the identity sequence to make sure the first record will get ID 1. Then we get the user repository from the dataSource
and insert our first user object into the table using the repository.
Now we need to tell the dataSource that we have this seeder class. To do that, let's update our src/db/data-source.ts
file.
// src/db/data-source.ts
import { SeederOptions } from 'typeorm-extension';
...
export const dataSourceOptions: DataSourceOptions & SeederOptions = {
...
seeds: ['dist/db/seeds/**/*.js'],
};
We have added seeds
array into our dataSourceOptions
object. Notice, we are using the glob pattern for pointing to the seeds
directory inside the dist/db/
directory. The glob pattern will not work if you point to the typescript files inside src/db/seeds/
directory. Since our build command will generate the dist directory anyway, there is no harm using the .js
files from there.
Now, add a script for seeding in the package.json
.
// package.json
{
...
"scripts": {
...
"seed": "npm run build && ts-node ./node_modules/typeorm-extension/dist/cli/index.js seed -d ./src/db/data-source.ts"
}
}
In this command, we build the project first, then run the seed command from typeorm-extension
with the help of ts-node
.
Note: using the
-d ./src/db/data-source.ts
we are passing the path to the datasource file to the seed command.
Let's invoke the script.
npm run seed
Once we execute the command the user
table in the database should be populated with one user.
Factories to generate bulk data
To generate random data for an entity, create a factory for the desired entity.
The factory callback provides an instance of the faker library as a function argument, to populate the entity with random data.
Create a factory function in src/db/factories/user.factory.ts
file.
// src/db/factories/user.factory.ts
import { setSeederFactory } from 'typeorm-extension';
import { User } from '../../resources/users/entities/user.entity';
export default setSeederFactory(User, (faker) => {
const user = new User();
const sexFlag = faker.number.int(1);
const sex: 'male' | 'female' = sexFlag ? 'male' : 'female';
user.firstName = faker.person.firstName(sex);
user.lastName = faker.person.lastName(sex);
return user;
});
Now update the user.seeder.ts
file and utilize this factory function for generating random users.
// src/db/seeds/user.seeder.ts
...
export default class UserSeeder implements Seeder {
public async run(
dataSource: DataSource,
factoryManager: SeederFactoryManager,
): Promise<void> {
...
const userFactory = factoryManager.get(User);
// save 1 factory generated entity, to the database
await userFactory.save();
// save 5 factory generated entities, to the database
await userFactory.saveMany(5);
}
}
Since we are using SeederFactoryManager
instance to get the user factory, we need to update our dataSourceOptions
and make it aware of the factory files.
// src/db/data-source.ts
...
export const dataSourceOptions: DataSourceOptions & SeederOptions = {
...
factories: ['dist/db/factories/**/*.js'],
};
Our factory setup is done. Run the seed
command again.
npm run seed
Check the user
table, it should have 1 hardcoded user and 6 more random users.
Seed programmatically
It is also possible to seed the database from the codebase, without using the CLI commands. To do that, we need to get access to the DataSource
instance. There is a function called runSeeders
exported from typeorm-extension
which can be used to seed data programmatically. This is especially useful in e2e tests. You can seed your in-memory test database on-the-fly before each test. Probably I'll cover this in another article for testing a NestJS application.
Conclusion
We have successfully configured SQL database seeding functionality using TypeORM and typeorm-extension package. We also get some insights on how to seed programmatically using runSeeders
function provided by typeorm-extension.
You can find the full project here: https://github.com/mazid1/nestjs-typeorm/tree/feature/seed
In the next article, I'll write about how to test a NestJS application.
I'm open for a remote full-time/contract/freelance job for the position of Full-stack Software Engineer. If you are hiring, ping me at mazidmailbox@gmail.com