A simple but powerful database seeder for TypeORM ^0.3.0
A Concepta Fork of TypeORM Seeding
Originally authored and maintained by Gery Hirschfeld, Jorge Bodega and Contributors
Creating sample data for your TypeORM project entities is exhausting 🥵
The TypeORM seeding module's goal is to make this task fun and rewarding.
How does it work? Just create your entity factories and/or seeders, and run the CLI command!
@Entity()
class User {
@PrimaryGeneratedColumn('uuid')
id: string
@Column()
name: string
@Column()
lastname: string
}
class UserFactory extends Factory<User> {
protected async entity(): Promise<User> {
const user = new User()
user.name = 'John'
user.lastname = 'Doe'
return user
}
}
export class UserSeeder extends Seeder {
async run() {
await this.factory(UserFactory).create()
}
}
typeorm-seeding seed -c seeding-source.js
This module requires TypeORM 0.3.0 and higher.
Please read the TypeORM Getting Started documentation. This explains how to setup a TypeORM project.
Install the package with npm
or yarn
. Add the development flag if you are not using seeders and/or factories in production code.
The Faker package was previously a dependency of the project, but now it is optional. If you want to use faker, you will need to install and import it.
npm i [-D] @concepta/typeorm-seeding @faker-js/faker
yarn add [-D] @concepta/typeorm-seeding @faker-js/faker
The heart of the module is the SeedingSource
class.
You must create an instance and provide it to the CLI via configuration file,
or pass it directly to your Seeder
and Factory
constructors.
class SeedingSource {
constructor(options: SeedingSourceOptions)
}
The SeedingSource
instance is configured with a TypeORM DataSource instance (or options to create an instance),
a list of Seeder
classes, and optionally a list of default Seeder
classes.
interface SeedingSourceOptions {
// data source instance, or options for creating a data source instance
dataSource: DataSource | DataSourceOptions
// all of your seeder classes, REQUIRED for CLI
seeders?: ClassConstructor<Seeder>[]
// default seeders (if provided, only these will be run by the CLI)
defaultSeeders?: ClassConstructor<Seeder>[]
}
The DataSource
config can be defined directly in the seeding source module, or imported from a different module.
Since most developers have this in it's own file, our example will follow this standard.
const { DataSource } = require('typeorm')
const { User, Pet } = require('./my-module')
module.exports = new DataSource({
type: 'sqlite',
database: ':memory:',
entities: [User, Pet],
})
We import the above DataSource
and our seeders from my-module
.
const { SeedingSource } = require('@concepta/typeorm-seeding')
const { AppSeeder, UserSeeder, PetSeeder, dataSource } = require('./my-module')
module.exports = new SeedingSource({
dataSource, // overridden if provided by CLI arg
seeders: [UserSeeder, PetSeeder],
})
You must initialize your SeedingSource
instance or manually set an
already intialized instance of DataSource
before you make any queries.
When you are seeding via the CLI command, these steps are done for you automatically at runtime.
// get your seeding source instance
const { seedingSource } = require('./seeding-source')
// initialize the Data Source that was passed via options
await seedingSource.initialize()
const { dataSource } = require('./data-source')
const { seedingSource } = require('./seeding-source')
// initialize the Data Source manually
await dataSource.initialize()
// set on the Seeding Source
seedingSource.dataSource = dataSource
Factory is how we provide a way to simplify entities creation, implementing a factory creational pattern. It is defined as an abstract class with generic typing, so you have to extend it.
Note: It is possible to create more than one
Factory
related to the same entity class.
class UserFactory extends Factory<User> {
// required
protected async entity(): Promise<User> {
const user = new User()
user.name = 'John'
user.lastname = 'Doe'
return user
}
// optional
protected async finalize(user: User): Promise<void> {
// last chance to mutate the entity at end of factory lifecycle
}
}
This method must be overridden to define how the entity is generated. It is called to instantiate the entity and the result will be used for the rest of factory lifecycle.
In the most basic implementation, the factory is responsible for creating the new entity instance.
class UserFactory extends Factory<User> {
protected async entity(): Promise<User> {
const user = new User()
user.name = 'John'
user.lastname = 'Doe'
return user
}
}
If you configure the entity option, then the method receives a new instance of that entity class.
This factory is now eligible for entity overrides.
class UserFactory extends Factory<User> {
protected options = {
entity: User,
}
protected async entity(user: User): Promise<User> {
user.name = 'John'
user.lastname = 'Doe'
return user
}
}
This method can be overridden to customize how the entity is finalized. It is called at the end of the entity generation lifecycle, giving one last opportunity to set defaults or perform data validation, etc.
protected async finalize(pet: Pet): Promise<void> {
if (!pet.owner) {
pet.owner = await this.factory(UserFactory).create()
}
}
The make()
method executes the factory lifecycle and returns a new instance of the given entity.
The instance is filled with the generated values from the factory lifecycle, but not saved in the database.
Important: You must pass the entity to the factory's
save()
method to persist the entity if desired.
make(overrideParams: Partial<Entity> = {}): Promise<Entity>
export class UserSeeder extends Seeder {
async run() {
// get a user factory
const userFactory = this.factory(UserFactory)
// using defaults
const user = userFactory.make()
// with email override
const user2 = userFactory.make({ email: 'other@mail.com' })
// persist to database (optional)
await userFactory.save([user, user2])
}
}
The makeMany()
method executes the factory lifecycle and returns many new instances of the given entity.
Each instance is filled with the generated values from the factory lifecycle, but not saved in the database.
Important: You must pass the entities to the factory's
save()
method to persist the entities if desired.
makeMany(amount: number, overrideParams: Partial<Entity> = {}): Promise<Entity>
export class UserSeeder extends Seeder {
async run() {
// get a user factory
const userFactory = this.factory(UserFactory)
// using defaults
const users = userFactory.makeMany(10)
// with email override
const users2 = userFactory.makeMany(10, { email: 'other@mail.com' })
// persist to database (optional)
await userFactory.save(users)
await userFactory.save(users2)
}
}
The create()
method is similar to the make()
method,
but at the end the generated entity instance gets persisted in
the database using the TypeORM entity manager.
create(overrideParams: Partial<Entity> = {}, saveOptions?: SaveOptions): Promise<Entity>
export class UserSeeder extends Seeder {
async run() {
// get a user factory
const userFactory = this.factory(UserFactory)
// using default
await userFactory.create()
// override the email
await userFactory.create({ email: 'other@mail.com' })
// using save options
await userFactory.create({ email: 'other@mail.com' }, { listeners: false })
}
}
The createMany()
methods is similar to the makeMany()
method,
but at the end the generated entity instances gets persisted in
the database using the TypeORM entity manager.
createMany(amount: number, overrideParams: Partial<Entity> = {}, saveOptions?: SaveOptions): Promise<Entity>
export class UserSeeder extends Seeder {
async run() {
// get a user factory
const userFactory = this.factory(UserFactory)
// using default
await userFactory.createMany(10)
// override the email
await userFactory.createMany(10, { email: 'other@mail.com' })
// using save options
await userFactory.createMany(10, { email: 'other@mail.com' }, { listeners: false })
}
}
Use the .map()
function to alter the generated value for each entity.
This is especially useful for setting different data for each generated entity with makeMany()
and createMany()
,
compared to overrideParams
which sets the same data for all generated entities.
map(mapFunction: (entity: Entity) => void): Factory
import { faker } from '@faker-js/faker'
export class UserSeeder extends Seeder {
async run() {
await this.factory(UserFactory)
.map((user) => {
user.favoriteColor = faker.color.human()
})
.createMany(10)
}
}
Use the .factory()
utility method to get an instance of a Factory
class dependency.
This is the recommended way to obtain a factory instance, as it automatically sets the seeding source, as well as supports the factory overrides api.
The seeding source of the calling Factory is automatically set on the new factory.
factory<T>(factory: ClassConstructor<ExtractFactory<T>>): ExtractFactory<T>
class UserFactory extends Factory<User> {
protected options = {
entity: User,
}
protected async entity(user: User): Promise<User> {
user.name = 'John'
user.lastname = 'Doe'
user.pet = await this.factory(PetFactory).create()
return user
}
}
Use the .save()
utility method to persist entities to the database, that were not automatically persisted.
async save(entity: Entity, saveOptions?: SaveOptions): Promise<Entity>
async save(entities: Entity[], saveOptions?: SaveOptions): Promise<Entity[]>
export class UserSeeder extends Seeder {
async run() {
const userFactory = this.factory(UserFactory)
const user = await userFactory.make()
await userFactory.save(user)
}
}
The complete factory lifecycle is explained in the following table.
Priority | Step | |
---|---|---|
1 | map() |
Entity is passed to map function (if provided) |
2 | paramOverrides |
Param overrides passed to make() , makeMany() ,create() , createMany() are applied |
3 | Promises |
Entity attributes which are promises are resolved |
4 | Factories |
Entity attributes which are factory instances are executed (.make() or .create() ). |
5 | finalize() |
Entity is passed to finalize. No further processing is done after this. |
The Seeder class provides a way to orchestrate the use of factories to insert data into the database.
It is an abstract class with one method to be implemented: run()
.
Note: Seeder classes are required for seeding from the CLI. However, you can create your own seeding scripts using only Factory classes.
class UserSeeder extends Seeder {
async run() {
// use factories to generate and persist entities
await this.factory(UserFactory).createMany(10)
}
}
This function must be defined when extending the class.
run(): Promise<void>
async run() {
await this.factory(UserFactory).createMany(10)
}
Use the .factory()
utility method to get an instance of a Factory
class.
This is the recommended way to obtain a factory instance, as it automatically sets the seeding source, as well as supports the factory overrides api.
The seeding source of the calling Seeder is automatically set on the returned factory.
factory<T>(factory: ClassConstructor<ExtractFactory<T>>): ExtractFactory<T>
export class UserSeeder extends Seeder {
async run() {
const userFactory = this.factory(UserFactory)
await userFactory.create()
}
}
This method enables you to create a tree structure of seeders.
protected async call(seeders?: SeederInstanceOrClass[]): Promise<void>
Call one or more seeders explicitly.
export class UserSeeder extends Seeder {
async run() {
await this.factory(UserFactory).createMany(10)
await this.call([PetSeeder])
}
}
Call one or more seeders via options.
This seeder is now eligible for seeder overrides.
export class UserSeeder extends Seeder {
protected options: {
seeders: [PetSeeder]
}
async run() {
await this.factory(UserFactory).createMany(10)
await this.call()
}
}
If your seeders use a tree structure, you can use the
defaultSeeders
option to determine the entry point(s) of your seeder tree(s)
There are two possible commands to execute, one to see the current configuration and one to run seeders.
Add the following scripts to your package.json
file to configure them.
"scripts": {
"seed:config": "typeorm-seeding config -r ./dist -c seeding-source.js",
"seed:run": "typeorm-seeding seed -r ./dist -c seeding-source.js",
}
This command prints the seeder configuration.
typeorm-seeding config
Example result
{
seeders: [ [class User], [class Pet] ],
defaultSeeders: [ [class AppSeeder] ],
dataSource: [ [class DataSource] ]
}
Option | Default | Description |
---|---|---|
--root or -r |
process.cwd() |
Path to the project root |
--seedingSource or -c |
Relative path to the seeding config from root . |
This command executes your seeder(s).
typeorm-seeding seed
Option | Default | Description |
---|---|---|
--root or -r |
process.cwd() |
Path to the project root |
--seedingSource or -c |
Relative path to the seeding config from root . |
|
--dataSource or -d |
Relative path to TypeORM data source config from root . |
|
--seed or -s |
One or more specific seeder class(es) to run individually. |
The unit testing features allow you to use Factories and Seeders completely independently of the CLI.
SeedingSources and DataSources can be instanitated at run time and directly injected into Factories and Seeders.
Additionally, you can override Factory and Seeder dependencies via class and constructor options.
To seed your unit tests with Seeders, a Runner
class instance is provided on SeedingSource
.
To run one or more seeders with one command, use the SeedingSource.run
instance.
Execute all seeders.
SeedingSource.run.all(): Promise<void>
Execute one seeder.
SeedingSource.run.one(
seeder: SeederInstanceOrClass,
): Promise<void>
Execute many seeders.
SeedingSource.run.many(
seeders: SeederInstanceOrClass[],
): Promise<void>
Execute all default seeders.
SeedingSource.run.defaults(): Promise<void>
Factories can be used stand alone if you explicitly pass a SeedingSource instance option to the constructor.
import { seedingSource } from './my-module'
const factory = new UserFactory({ seedingSource })
const user = await factory.create()
If a factory gets a Factory
dependency with the factory()
method, you can override it using the overrides api.
import { faker } from '@faker-js/faker'
import { seedingSource } from './my-module'
class UserFactory extends Factory<User> {
protected options = {
entity: User,
}
protected async entity(user: User): Promise<User> {
user.name = 'John'
user.lastname = 'Doe'
user.pet = await this.factory(PetFactory).create()
return user
}
}
class PetFactory2 extends Factory<Pet> {
protected options = {
entity: Pet,
override: PetFactory,
}
async entity(pet: Pet) {
pet.color = faker.color.human()
return pet
}
}
const factory = new UserFactory({
seedingSource,
factories: [new PetFactory2()],
})
// PetFactory2 is used to generate Pet entities
const user = await factory.create()
All Factory
overrides are supported via the constructor for just-in-time operations.
constructor(optionOverrides?: FactoryOptionsOverrides<Entity>);
interface FactoryOptionsOverrides<T, SF = any> {
entity?: ClassConstructor<T>
factories?: ExtractFactory<SF>[]
override?: ClassConstructor<Factory<any>>
seedingSource?: SeedingSource
}
Seeders can be used stand alone if you explicitly pass a SeedingSource instance option to the constructor.
import { seedingSource } from './my-module'
const userSeeder = new UserSeeder({ seedingSource })
await userSeeder.run()
If a seeder gets a Factory
dependency with the factory()
method, you can override it using the overrides api.
import { faker } from '@faker-js/faker'
import { seedingSource } from './my-module'
export class UserSeeder extends Seeder {
async run() {
await this.factory(UserFactory).createMany(10)
}
}
class UserFactory2 extends Factory<User> {
protected options = {
entity: User,
override: UserFactory,
}
async entity(user: User) {
user.favoriteColor = faker.color.human()
return user
}
}
const userSeeder = new UserSeeder({
seedingSource,
factories: [new UserFactory2()],
})
// UserFactory2 is used to generate User entities
await userSeeder.run()
If a seeder has the seeders options set, you can override them using the overrides api.
Important: When you override seeders, the entire list of seeders options is replaced, NOT merged.
import { faker } from '@faker-js/faker'
import { seedingSource } from './my-module'
export class UserSeeder extends Seeder {
protected options: {
seeders: [PetSeeder, FoodSeeder]
}
async run() {
await this.factory(UserFactory).createMany(10)
await this.call()
}
}
export class PetSeederOverride extends Seeder {
async run() {
await this.factory(PetFactory)
.map((pet) => {
pet.color = faker.color.human()
})
.createMany(20)
}
}
const userSeeder = new UserSeeder({
seedingSource,
seeders: [PetSeederOverride],
})
// UserSeeder and PetSeederOverride are executed, FoodSeeder is NOT
await userSeeder.run()
All Seeder
overrides are supported via the constructor for just-in-time operations.
constructor(optionOverrides?: SeederOptionsOverrides);
interface SeederOptionsOverrides<SF = any> {
seeders?: SeederInstanceOrClass[]
factories?: ExtractFactory<SF>[]
seedingSource?: SeedingSource
}