How to seed a database with TypeORM and Faker in 2023

How to seed a database
By Andrianarisoa Daniel

So, I was playing around with TypeORM and at some point I needed to populate my database with some data in order to be able to test some features as it 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 intersted 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 absolutely need to install TypeORM and a database adapter, in our case we are going to use mysql:

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

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

users.entity.ts
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  OneToMany,
} from "typeorm";

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

  @Column()
    userName?: string;

  @OneToMany(() => Post, (post) => post.author)
    posts?: Post[];
}
posts.entity.ts
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:

users.factory.ts
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 are being generated like so:

posts.factory.ts
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 in order 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 much a seeders as you want. But to make things simpler in general, I think only one is necessary and we'll put it in file named main.seeder.ts. The minimal code needed to create a seeder is as following:

main.seeder.ts
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:

main.seeder.ts
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 generating and save 7 users to the database.

Now let's see how to seed the posts. This time, in addition to the number of the 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:

main.seeder.ts
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 wich we generate random posts with the method make(). That method does not save any records in the database, iit 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 definitely 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 elemen from a given array: in our case from the previously genereated users.

We wrapped everything just discussed above in Promise.all() to take advantage of node.js asynchronous abilities at the most and await for 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:

seed.ts
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 datasource configuration and initialization. The only new porperties there are factories and seeds which are brought by typeorm-extension. Hence as their names suggest, they are pretty straighforward 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 datas by giving it an argument true.

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

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

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

Then install the package ts-node:

terminal
yarn add -D ts-node

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

terminal
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 on how to leverage its power. So, I'd suggest you to give an eye on its documentation here.

Happy hacking and happy seeding!!