Campaign organizers! Consul Caesar.js noticed that every time a legion marches out, it must reload basic supplies from scratch. That's a waste of time! It's time to learn about creating seeds and fixtures - ready-made data sets that will automatically supply our legion with everything needed for a successful campaign!
Seeders are like experienced quaestors who know exactly what to load onto the wagons before a march:
1// seeds/legionaries.seeder.ts
2import { DataSource } from 'typeorm';
3import { Seeder, SeederFactoryManager } from 'typeorm-extension';
4import { Legionary } from '../src/legionaries/legionaries.entity';
5import { Centurion } from '../src/centurion/centurion.entity';
6import { Skill } from '../src/skill/skill.entity';
7
8export default class LegionarySeeder implements Seeder {
9 public async run(
10 dataSource: DataSource,
11 factoryManager: SeederFactoryManager
12 ): Promise<any> {
13 const legionariesRepository = dataSource.getRepository(Legionary);
14 const centurionRepository = dataSource.getRepository(Centurion);
15 const skillRepository = dataSource.getRepository(Skill);
16
17 // 1. Check if data already exists
18 const existingLegionarysCount = await legionariesRepository.count();
19 if (existingLegionarysCount > 0) {
20 console.log('Legionarys already seeded, skipping...');
21 return;
22 }
23
24 // 2. Create skills
25 const skills = await skillRepository.save([
26 {
27 name: 'Navigation',
28 description: 'Ability to lead cohorts across dangerous provinces',
29 difficulty: 'Intermediate'
30 },
31 {
32 name: 'Sword Fighting',
33 description: 'Master the art of swordsmanship',
34 difficulty: 'Advanced'
35 },
36 {
37 name: 'Tribute Hunting',
38 description: 'Finding hidden tributes using maps and clues',
39 difficulty: 'Expert'
40 },
41 {
42 name: 'Legion Repair',
43 description: 'Maintaining and repairing cohort components',
44 difficulty: 'Basic'
45 }
46 ]);
47
48 // 3. Create centurions
49 const centurions = await centurionRepository.save([
50 {
51 name: 'Marcus Cicero',
52 cohortName: 'Legio X Equestris',
53 yearsOfExperience: 15,
54 totalTributeValue: 50000
55 },
56 {
57 name: 'Aurelia Bonna',
58 cohortName: 'Legio IX Hispana',
59 yearsOfExperience: 8,
60 totalTributeValue: 25000
61 },
62 {
63 name: 'Bartholomeus Rufus',
64 cohortName: 'Legio XIII Gemina',
65 yearsOfExperience: 12,
66 totalTributeValue: 40000
67 }
68 ]);
69
70 // 4. Create legionaries
71 const legionaries = [
72 {
73 name: 'Gaius Maximus',
74 rank: 'Speculator',
75 tributeCount: 15,
76 totalTributeValue: 12000,
77 isActive: true,
78 centurion: centurions[0],
79 skills: [skills[0], skills[2]] // Navigation and Tribute Collection
80 },
81 {
82 name: 'Lucius Verus',
83 rank: 'Coquus',
84 tributeCount: 8,
85 totalTributeValue: 6000,
86 isActive: true,
87 centurion: centurions[0],
88 skills: [skills[1], skills[3]] // Swordsmanship and Equipment Repair
89 },
90 {
91 name: 'Livia Drusilla',
92 rank: 'Optio',
93 tributeCount: 20,
94 totalTributeValue: 18000,
95 isActive: true,
96 centurion: centurions[1],
97 skills: [skills[0], skills[1]] // Navigation and Swordsmanship
98 }
99 ];
100
101 await legionariesRepository.save(legionaries);
102
103 console.log('Legionary legion successfully seeded! ');
104 }
105}1// seeds/tribute.seeder.ts
2import { DataSource } from 'typeorm';
3import { Seeder, SeederFactoryManager } from 'typeorm-extension';
4import { Tribute } from '../src/tribute/tribute.entity';
5import { Legionary } from '../src/legionaries/legionaries.entity';
6import { TributeMap } from '../src/tribute-map/tribute-map.entity';
7
8export default class TributeSeeder implements Seeder {
9 public async run(
10 dataSource: DataSource,
11 factoryManager: SeederFactoryManager
12 ): Promise<any> {
13 const tributeRepository = dataSource.getRepository(Tribute);
14 const legionariesRepository = dataSource.getRepository(Legionary);
15 const tributeMapRepository = dataSource.getRepository(TributeMap);
16
17 // Check if tributes already exist
18 const existingTributes = await tributeRepository.count();
19 if (existingTributes > 0) {
20 console.log('Tributes already seeded, skipping...');
21 return;
22 }
23
24 // Fetch existing legionaries
25 const legionaries = await legionariesRepository.find();
26 if (legionaries.length === 0) {
27 console.log('No legionaries found, please run LegionarySeeder first');
28 return;
29 }
30
31 // Tribute type definitions
32 const tributeTypes = [
33 {
34 type: 'gold',
35 items: [
36 { name: 'Golden Aureus', baseValue: 100 },
37 { name: 'Golden Chalice', baseValue: 500 },
38 { name: 'Crown of Jupiter', baseValue: 5000 }
39 ]
40 },
41 {
42 type: 'silver',
43 items: [
44 { name: 'Silver Denarius', baseValue: 50 },
45 { name: 'Silver Gladius', baseValue: 300 },
46 { name: 'Silver Signet', baseValue: 800 }
47 ]
48 },
49 {
50 type: 'jewels',
51 items: [
52 { name: 'Ruby Ring', baseValue: 1200 },
53 { name: 'Emerald Necklace', baseValue: 2000 },
54 { name: 'Diamond Tiara', baseValue: 8000 }
55 ]
56 },
57 {
58 type: 'artifacts',
59 items: [
60 { name: 'Ancient Scroll', baseValue: 1500 },
61 { name: 'Mystical Medallion', baseValue: 3000 },
62 { name: 'Cursed Relic', baseValue: 666, isCursed: true }
63 ]
64 }
65 ];
66
67 const locations = [
68 'Rome',
69 'Pompeii',
70 'Capua',
71 'Ostia',
72 'Carthago',
73 'Alexandria',
74 'Mare Nostrum',
75 'Palatine Vault'
76 ];
77
78 const tributesToCreate = [];
79 const tributeMapsToCreate = [];
80
81 tributeTypes.forEach(category => {
82 category.items.forEach(item => {
83 const randomLegionary = legionaries[Math.floor(Math.random() * legionaries.length)];
84 const randomLocation = locations[Math.floor(Math.random() * locations.length)];
85 const valueVariation = 0.8 + Math.random() * 0.4; // +/-20% value
86
87 const tribute = {
88 name: item.name,
89 type: category.type,
90 value: Math.round(item.baseValue * valueVariation),
91 description: `A magnificent ${item.name.toLowerCase()} found in the depths of ${randomLocation}`,
92 location: randomLocation,
93 isCursed: item.isCursed || false,
94 collectedAt: this.getRandomPastDate(),
95 legionaries: randomLegionary
96 };
97
98 tributesToCreate.push(tribute);
99
100 // Some tributes have maps
101 if (Math.random() < 0.3) { // 30% chance of a map
102 tributeMapsToCreate.push({
103 islandName: randomLocation,
104 latitude: this.getRandomLatitude(),
105 longitude: this.getRandomLongitude(),
106 instructions: this.generateTributeInstructions(),
107 isFound: true, // Since we have the tribute, the map was used
108 legionaries: randomLegionary
109 });
110 }
111 });
112 });
113
114 // Save tributes and maps
115 await tributeRepository.save(tributesToCreate);
116 await tributeMapRepository.save(tributeMapsToCreate);
117
118 console.log(`Created ${tributesToCreate.length} tributes and ${tributeMapsToCreate.length} tribute maps! `);
119 }
120
121 private getRandomPastDate(): Date {
122 const now = new Date();
123 const pastDays = Math.floor(Math.random() * 365 * 2); // Last 2 years
124 return new Date(now.getTime() - pastDays * 24 * 60 * 60 * 1000);
125 }
126
127 private getRandomLatitude(): number {
128 return (Math.random() - 0.5) * 180; // -90 to 90
129 }
130
131 private getRandomLongitude(): number {
132 return (Math.random() - 0.5) * 360; // -180 to 180
133 }
134
135 private generateTributeInstructions(): string {
136 const instructions = [
137 'Walk 50 paces north from the old oak tree',
138 'Find the eagle-shaped rock near the beach',
139 'Look for the X marked on the ancient stone',
140 'Dig beneath the palm tree with the broken trunk',
141 'Search near the cave entrance on the east side',
142 'Follow the stream until you reach the waterfall'
143 ];
144
145 const chosen = [];
146 const count = 2 + Math.floor(Math.random() * 3); // 2-4 instructions
147
148 for (let i = 0; i < count; i++) {
149 const randomIndex = Math.floor(Math.random() * instructions.length);
150 chosen.push(instructions[randomIndex]);
151 instructions.splice(randomIndex, 1);
152 }
153
154 return chosen.join('. ') + '.';
155 }
156}1// factories/legionaries.factory.ts
2import { define } from 'typeorm-seeding';
3import { Legionary } from '../src/legionaries/legionaries.entity';
4import { Faker } from '@faker-js/faker';
5
6define(Legionary, (faker: Faker) => {
7 const legionaries = new Legionary();
8
9 legionaries.name = faker.person.fullName();
10 legionaries.rank = faker.helpers.arrayElement([
11 'Tiro', 'Tesserarius', 'Speculator', 'Optio', 'Centurion'
12 ]);
13 legionaries.tributeCount = faker.number.int({ min: 0, max: 50 });
14 legionaries.totalTributeValue = faker.number.int({ min: 0, max: 100000 });
15 legionaries.isActive = faker.datatype.boolean();
16 legionaries.joinedAt = faker.date.past({ years: 5 });
17 legionaries.lastSeenAt = faker.date.recent({ days: 30 });
18
19 // Skills as JSON
20 legionaries.skills = {
21 navigation: faker.number.int({ min: 1, max: 100 }),
22 combat: faker.number.int({ min: 1, max: 100 }),
23 negotiation: faker.number.int({ min: 1, max: 100 }),
24 leadership: faker.number.int({ min: 1, max: 100 })
25 };
26
27 legionaries.biography = faker.lorem.paragraphs(2);
28
29 return legionaries;
30});
31
32// factories/tribute.factory.ts
33import { define } from 'typeorm-seeding';
34import { Tribute } from '../src/tribute/tribute.entity';
35import { Faker } from '@faker-js/faker';
36
37define(Tribute, (faker: Faker) => {
38 const tribute = new Tribute();
39
40 const tributeNames = [
41 'Golden Coin', 'Silver Chalice', 'Ruby Ring', 'Diamond Necklace',
42 'Ancient Map', 'Mystical Orb', 'Cursed Medallion', 'Crystal Eagle'
43 ];
44
45 tribute.name = faker.helpers.arrayElement(tributeNames);
46 tribute.type = faker.helpers.arrayElement(['gold', 'silver', 'jewels', 'artifacts']);
47 tribute.value = faker.number.int({ min: 100, max: 10000 });
48 tribute.description = faker.lorem.sentence();
49 tribute.location = faker.location.city();
50 tribute.isCursed = faker.datatype.boolean(0.1); // 10% chance of curse
51 tribute.collectedAt = faker.date.past({ years: 2 });
52
53 return tribute;
54});1// seeds/factory.seeder.ts
2import { DataSource } from 'typeorm';
3import { Seeder, SeederFactoryManager } from 'typeorm-extension';
4import { Legionary } from '../src/legionaries/legionaries.entity';
5import { Tribute } from '../src/tribute/tribute.entity';
6
7export default class FactorySeeder implements Seeder {
8 public async run(
9 dataSource: DataSource,
10 factoryManager: SeederFactoryManager
11 ): Promise<any> {
12 // Create 50 random legionaries
13 const legionariesFactory = factoryManager.get(Legionary);
14 const legionaries = await legionariesFactory.saveMany(50);
15
16 // Create 200 random tributes
17 const tributeFactory = factoryManager.get(Tribute);
18 const tributes = await tributeFactory.saveMany(200);
19
20 // Assign tributes to legionaries
21 for (const tribute of tributes) {
22 if (Math.random() < 0.8) { // 80% of tributes have an owner
23 const randomLegionary = legionaries[Math.floor(Math.random() * legionaries.length)];
24 tribute.legionaries = randomLegionary;
25 }
26 }
27
28 await dataSource.getRepository(Tribute).save(tributes);
29
30 console.log(`Created ${legionaries.length} legionaries and ${tributes.length} tributes using factories!`);
31 }
32}1// fixtures/basic-skills.json
2[
3 {
4 "name": "Navigation",
5 "description": "Ability to lead cohorts across dangerous provinces",
6 "difficulty": "Intermediate"
7 },
8 {
9 "name": "Sword Fighting",
10 "description": "Master the art of swordsmanship",
11 "difficulty": "Advanced"
12 },
13 {
14 "name": "Tribute Hunting",
15 "description": "Finding hidden tributes using maps and clues",
16 "difficulty": "Expert"
17 },
18 {
19 "name": "Legion Repair",
20 "description": "Maintaining and repairing cohort components",
21 "difficulty": "Basic"
22 },
23 {
24 "name": "Ballista Operation",
25 "description": "Operating and maintaining cohort ballistae",
26 "difficulty": "Intermediate"
27 },
28 {
29 "name": "Rope Work",
30 "description": "Siege rope and equipment handling skills",
31 "difficulty": "Basic"
32 }
33]1// seeds/json-fixture.seeder.ts
2import { DataSource } from 'typeorm';
3import { Seeder, SeederFactoryManager } from 'typeorm-extension';
4import { Skill } from '../src/skill/skill.entity';
5import * as fs from 'fs';
6import * as path from 'path';
7
8export default class JsonFixtureSeeder implements Seeder {
9 public async run(
10 dataSource: DataSource,
11 factoryManager: SeederFactoryManager
12 ): Promise<any> {
13 const skillRepository = dataSource.getRepository(Skill);
14
15 // Check if skills already exist
16 const existingSkills = await skillRepository.count();
17 if (existingSkills > 0) {
18 console.log('Skills already seeded, skipping...');
19 return;
20 }
21
22 // Load data from JSON file
23 const fixturePath = path.join(__dirname, '../fixtures/basic-skills.json');
24 const skillsData = JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
25
26 // Save skills
27 await skillRepository.save(skillsData);
28
29 console.log(`Loaded ${skillsData.length} skills from JSON fixture! `);
30 }
31}1// data-source.ts (update)
2import { DataSource, DataSourceOptions } from 'typeorm';
3import { SeederOptions } from 'typeorm-extension';
4
5const options: DataSourceOptions & SeederOptions = {
6 type: 'postgres',
7 host: 'localhost',
8 port: 5432,
9 username: 'centurion',
10 password: 'tribute123',
11 database: 'legionaries_cohort',
12 entities: ['src/**/*.entity{.ts,.js}'],
13 migrations: ['src/migrations/*{.ts,.js}'],
14
15 // Seed configuration
16 seeds: ['src/seeds/*{.ts,.js}'],
17 factories: ['src/factories/*{.ts,.js}'],
18};
19
20export const AppDataSource = new DataSource(options);1// package.json (adding scripts)
2{
3 "scripts": {
4 "seed:run": "ts-node ./node_modules/typeorm-extension/bin/cli.cjs seed:run --dataSource=./data-source.ts",
5 "seed:run:specific": "ts-node ./node_modules/typeorm-extension/bin/cli.cjs seed:run --dataSource=./data-source.ts --seed",
6 "db:setup": "npm run migration:run && npm run seed:run",
7 "db:reset": "npm run migration:revert && npm run migration:run && npm run seed:run"
8 }
9}1# Running seeds
2npm run seed:run
3
4# Running a specific seed
5npm run seed:run:specific LegionarySeeder
6
7# Full database reset with seeds
8npm run db:reset1// seeds/development.seeder.ts
2import { DataSource } from 'typeorm';
3import { Seeder, SeederFactoryManager } from 'typeorm-extension';
4
5export default class DevelopmentSeeder implements Seeder {
6 public async run(
7 dataSource: DataSource,
8 factoryManager: SeederFactoryManager
9 ): Promise<any> {
10 // Only in development environment
11 if (process.env.NODE_ENV !== 'development') {
12 console.log('Skipping development seeds - not in development environment');
13 return;
14 }
15
16 // Create lots of test data
17 const legionariesFactory = factoryManager.get(Legionary);
18 const tributeFactory = factoryManager.get(Tribute);
19
20 await legionariesFactory.saveMany(100);
21 await tributeFactory.saveMany(500);
22
23 console.log('Development environment seeded with test data!');
24 }
25}
26
27// seeds/production.seeder.ts
28export default class ProductionSeeder implements Seeder {
29 public async run(
30 dataSource: DataSource,
31 factoryManager: SeederFactoryManager
32 ): Promise<any> {
33 // Only basic data for production
34 if (process.env.NODE_ENV !== 'production') {
35 return;
36 }
37
38 // Only essential system data
39 await this.seedBasicSkills(dataSource);
40 await this.seedSystemSettings(dataSource);
41
42 console.log('Production environment seeded with essential data only!');
43 }
44
45 private async seedBasicSkills(dataSource: DataSource) {
46 // Basic skills needed in the system
47 const skillRepository = dataSource.getRepository(Skill);
48 // ... implementation
49 }
50
51 private async seedSystemSettings(dataSource: DataSource) {
52 // Basic system settings
53 // ... implementation
54 }
55}1// seeds/conditional.seeder.ts
2import { DataSource } from 'typeorm';
3import { Seeder, SeederFactoryManager } from 'typeorm-extension';
4
5export default class ConditionalSeeder implements Seeder {
6 public async run(
7 dataSource: DataSource,
8 factoryManager: SeederFactoryManager
9 ): Promise<any> {
10 // Check environment flag
11 const shouldSeedDemo = process.env.SEED_DEMO_DATA === 'true';
12 const shouldSeedTest = process.env.SEED_TEST_DATA === 'true';
13 const forceReseed = process.env.FORCE_RESEED === 'true';
14
15 if (shouldSeedDemo) {
16 await this.seedDemoData(dataSource, factoryManager, forceReseed);
17 }
18
19 if (shouldSeedTest) {
20 await this.seedTestData(dataSource, factoryManager, forceReseed);
21 }
22
23 // Check if this is the first run
24 const legionariesCount = await dataSource.getRepository(Legionary).count();
25 if (legionariesCount === 0 || forceReseed) {
26 await this.seedInitialData(dataSource);
27 }
28 }
29
30 private async seedDemoData(
31 dataSource: DataSource,
32 factoryManager: SeederFactoryManager,
33 force: boolean
34 ) {
35 console.log('Seeding demo data for presentations...');
36 // Create nice, presentable data
37 }
38
39 private async seedTestData(
40 dataSource: DataSource,
41 factoryManager: SeederFactoryManager,
42 force: boolean
43 ) {
44 console.log('Seeding test data for automated tests...');
45 // Create predictable data for tests
46 }
47
48 private async seedInitialData(dataSource: DataSource) {
49 console.log('Seeding initial required data...');
50 // Data required for the application to work
51 }
52}1// GOOD - check if data already exists
2async run(dataSource: DataSource) {
3 const repository = dataSource.getRepository(Entity);
4 const existingCount = await repository.count();
5
6 if (existingCount > 0) {
7 console.log('Data already exists, skipping...');
8 return;
9 }
10
11 // Perform seeding
12}
13
14// BAD - always add new data
15async run(dataSource: DataSource) {
16 // Always creates duplicates!
17 await repository.save(data);
18}1// Define seed order
2// 1. Basic entities (Skills, Ranks)
3// 2. Main entities (Legionaries, Centurions)
4// 3. Relational entities (Tributes, Maps)
5// 4. Test/demo data1// Different data for different environments
2if (process.env.NODE_ENV === 'test') {
3 // Predictable test data
4} else if (process.env.NODE_ENV === 'development') {
5 // Lots of diverse data
6} else {
7 // Only basic data
8}Congratulations! Now you're a master quaestor! You can:
✓ Create seeders for different data types ✓ Use Factory Pattern for generating tests ✓ Load data from JSON files as fixtures ✓ Configure seeders for different environments ✓ Implement conditional seeding with flags
Now your legion will always be ready to march with full supplies!