How to seed a database with TypeORM and Faker in 2023

Seeding our databases is very useful to help us have a grasp on our project's behavior in a development environment. Find out in this post how you can

How to seed a database with TypeORM and Faker in 2023

So, I was playing around with TypeORM and at some point, I needed to populate my database with some data to be able to test some features as they could be in real-world situations. Thankfully, we got our back covered thanks to the open-source community with the package typeorm-extension. It is a great package that just allows us to drop, create and seed databases using TypeORM. But we are most interested in the seeding part.

Enough Talk and Bring the Code!!

Naturally, we are going to install typeorm-extension and faker for genarting fake datas. And Of course, we need to install TypeORM and a database adapter, in our case we are going to use MySQL:

yarn add typeorm-extension @faker-js/faker typeorm reflect-metadata mysql

Then, let's assume that these are our entities:

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  OneToMany,
} from "typeorm";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
    id?: string;

  @Column()
    userName?: string;

  @OneToMany(() => Post, (post) => post.author)
    posts?: Post[];
}
import typeorm, {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  ManyToOne,
} from "typeorm";

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
    id?: string;

  @Column()
    title!: string;

  @Column()
    content!: string;

  @ManyToOne(() => User, (user) => user.posts)
    author!: typeorm.Relation<User>;
}

Factory in typeorm-extension for seeding

It is one of the two main concepts brought by the library we are about to use: typeorm-extension.

Basically for each entity we have in our application, we will define a factory and those will be responsible for generating the data that will populate our application. Each of these data corresponds to the properties that we have defined in our entity.

Starting with the User entity, the userName property should be generated so the corresponding factory would look like this:

import { Faker } from "@faker-js/faker";
import { setSeederFactory } from "typeorm-extension";
import { User } from "./users.entity";

export const UsersFactory = setSeederFactory(User, (faker: Faker) => {
  const user = new User();
  user.userName = faker.internet.userName();
  return user;
});

And for the Post entity, both title and content is being generated like so:

import { Faker } from "@faker-js/faker";
import { setSeederFactory } from "typeorm-extension";
import { Post } from "./posts.entity";

export const PostsFactory = setSeederFactory(Post, (faker: Faker) => {
  const post = new Post();
  post.title = faker.lorem.sentence();
  post.content = faker.lorem.sentence();
  return post;
});

Now with our factories defined, as soon as we define how many users or articles we want to create, the factory will always generate random values in each of the properties thanks to faker.js.

The Seeder in typeorm-extension

The second concept here is the Seeder.

A Seeder is a class we have to define to run the factories created above. In other words, we call the factories inside a Seeder then we call the Seeder to seed the database.

A seeder class must implement the Seeder interface. You can have as many seeders as you want. But to make things simpler in general, I think only one is necessary and we'll put it in a file named main.seeder.ts. The minimal code needed to create a seeder is as follows:

import { DataSource } from "typeorm";
import { Seeder, SeederFactoryManager } from "typeorm-extension";

export default class MainSeeder implements Seeder {
  public async run(
    dataSource: DataSource,
    factoryManager: SeederFactoryManager,
  ): Promise<any> {
    // Run the factories here
  }
}

Now let's seed the User entity:

import { DataSource } from "typeorm";
import { Seeder, SeederFactoryManager } from "typeorm-extension";
import { User } from "./users.entity";

export default class MainSeeder implements Seeder {
  public async run(
    dataSource: DataSource,
    factoryManager: SeederFactoryManager,
  ): Promise<any> {
    const userFactory = factoryManager.get(User);

    const users = await userFactory.saveMany(7);
  }
}

As simple as that, what we've done so far is generate and save 7 users to the database.

Now let's see how to seed the posts. This time, in addition to the number of posts to create, we also must provide the author of each post. Plus, we want to assign it randomly. Here is how to achieve it:

import { DataSource } from "typeorm";
import { Seeder, SeederFactoryManager } from "typeorm-extension";
import { faker } "@faker-js/faker";
import { User } from "./users.entity";
import { Post } from "./posts.entity";

export class MainSeeder implements Seeder {
  public async run(
    dataSource: DataSource,
    factoryManager: SeederFactoryManager,
  ): Promise<any> {
    const postsRepository = dataSource.getRepository(Post);

    const userFactory = factoryManager.get(User);
    const postsFactory = factoryManager.get(Post);

    const users = await userFactory.saveMany(7);

    const posts = await Promise.all(
      Array(17)
        .fill("")
        .map(async () => {
          const made = await postsFactory.make({
            author: faker.helpers.arrayElement(users),
          });
          return made;
        }),
    );
    await postsRepository.save(posts);
  }
}

Let me explain this code a little bit:

We wanted to create 17 posts, so we make an array of 17 items first and fill it with empty strings. It is important to fill our dynamic array with fill() otherwise the next call of map() won't work because the array is still an array of undefined items.

Then, we call the map() method inside of which we generate random posts with the method make(). That method does not save any records in the database, it only generates an entity instance and it accepts custom properties as parameters. For example, in our post factory, we omitted the assignment of the author field on purpose and it is here that we want to assign an author to our post. And we do so randomly with the helper function arrayElement from faker which returns a random element from a given array: in our case from the previously generated users.

We wrapped everything just discussed above in Promise.all() to take advantage of node.js asynchronous abilities at the most and await the result in the posts variable (or constant). Remember, those posts are not yet saved since we only used to call make() before.

Finally, It's time to save them on the last line by the mean of the posts repository.

Time to run the seeds

Create a file seed.ts and write down inside it the following code:

import "reflect-metadata";
import { DataSource, DataSourceOptions } from "typeorm";
import { runSeeders, SeederOptions } from "typeorm-extension";
import { User } from "./users.entity";
import { Post } from "./posts.entity";
import { UsersFactory } from "./users.factory";
import { PostsFactory } from "./users.factory";
import { MainSeeder } from "./main.seeder";

const {
  DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME,
} = process.env;

const options: DataSourceOptions & SeederOptions = {
  type: "mysql",
  host: DB_HOST || "localhost",
  port: Number(DB_PORT) || 3306,
  username: DB_USER || "test",
  password: DB_PASSWORD || "test",
  database: DB_NAME || "test",
  entities: [User, Post],
  // additional config options brought by typeorm-extension
  factories: [UsersFactory, PostsFactory],
  seeds: [MainSeeder],
};

const dataSource = new DataSource(options);

dataSource.initialize().then(async () => {
  await dataSource.synchronize(true);
  await runSeeders(dataSource);
  process.exit();
});

I assume that you are already familiar with TypeORM's data source configuration and initialization. The only new properties there are factories and seeds which are brought by typeorm-extension. Hence as their names suggest, they are pretty straightforward to understand.

Next to the initialization, we tell TypeORM to synchronize the database with a call to synchronize() method and especially, we drop the previous data by giving it an argument true.

Finally, we run the seeds with the runSeeders() function imported from typeorm-extension and then exits the process.

We want to be able to run the seeds as a run-script in our project, so in our package.json amend the the following line in the scripts field:

{
  //...
  "scripts": {
    //...
    "seed": "ts-node src/seeds.ts" // or whatever path to your seed file
  },
  // ...
}

Then install the package ts-node:

yarn add -D ts-node

Now you are going to be able to seed your database with the command:

yarn seed

Afterwords

The package typeorm-extension is just great, and I have not expanded its full specs in this post. What I've shown you here is just an opinionated approach to how to leverage its power. So, I'd suggest you give an eye on its documentation here.

Happy hacking and happy seeding!!