@concepta/typeorm-seeding v4.0.0-beta.1
Table of Contents
Introduction
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!
Entities
@Entity()
class User {
@PrimaryGeneratedColumn('uuid')
id: string
@Column()
name: string
@Column()
lastname: string
}
Factories
class UserFactory extends Factory<User> {
protected async entity(): Promise<User> {
const user = new User()
user.name = 'John'
user.lastname = 'Doe'
return user
}
}
Seeders
export class UserSeeder extends Seeder {
async run() {
await this.factory(UserFactory).create()
}
}
Run It!
typeorm-seeding seed -c seeding-source.js
Installation
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
Configuration
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.
Seeding Source Class
class SeedingSource {
constructor(options: SeedingSourceOptions)
}
Seeding Source Options
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>[]
}
Data Source Instance
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],
})
Seeding Source Instance
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],
})
Seeding Source Initialization
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.
Initialize DataSource passed via options
// get your seeding source instance
const { seedingSource } = require('./seeding-source')
// initialize the Data Source that was passed via options
await seedingSource.initialize()
Manually initialize DataSource and set on SeedingSource
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 Class
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
}
}
entity
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.
Basic Implementation
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
}
}
Advanced Implementation
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
}
}
finalize
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()
}
}
Factory Usage
make
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])
}
}
makeMany
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)
}
}
create
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 })
}
}
createMany
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 })
}
}
map
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)
}
}
factory
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
}
}
save
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)
}
}
Lifecycle Steps
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. |
Seeder Class
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)
}
}
run
This function must be defined when extending the class.
run(): Promise<void>
async run() {
await this.factory(UserFactory).createMany(10)
}
Seeder Usage
factory
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()
}
}
call
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)
CLI
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",
}
config
This command prints the seeder configuration.
typeorm-seeding config
Example result
{
seeders: [ [class User], [class Pet] ],
defaultSeeders: [ [class AppSeeder] ],
dataSource: [ [class DataSource] ]
}
Options
Option | Default | Description |
---|---|---|
--root or -r | process.cwd() | Path to the project root |
--seedingSource or -c | Relative path to the seeding config from root . |
seed
This command executes your seeder(s).
typeorm-seeding seed
Options
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. |
Unit Testing
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.
Runner Class
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.
SeedingSource.run.all
Execute all seeders.
SeedingSource.run.all(): Promise<void>
SeedingSource.run.one
Execute one seeder.
SeedingSource.run.one(
seeder: SeederInstanceOrClass,
): Promise<void>
SeedingSource.run.many
Execute many seeders.
SeedingSource.run.many(
seeders: SeederInstanceOrClass[],
): Promise<void>
SeedingSource.run.defaults
Execute all default seeders.
SeedingSource.run.defaults(): Promise<void>
Factories
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
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
}
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago