Seeding
Demo data lives in the seed crate — a product crate under crates/,
beside migrations. Seeding is idempotent: every
factory inserts with ON CONFLICT DO NOTHING, so nestrs run db seed can run any
number of times and only ever fills gaps. nestrs run db reset chains a fresh
migrate with a seed for a clean slate.
A factory
Section titled “A factory”One factory per table, each a seed(db) function that inserts its demo rows
and returns how many landed. Organizations use fixed UUIDs so other factories
— and tests — can reference them by constant:
pub const ACME: Uuid = Uuid::from_u128(0x0000_0000_0000_0000_0000_0000_0000_ac3e);pub const GLOBEX: Uuid = Uuid::from_u128(0x0000_0000_0000_0000_0000_0000_0000_61b3);
const DEMO_ORGS: [(Uuid, &str); 2] = [(ACME, "Acme"), (GLOBEX, "Globex")];
pub async fn seed(db: &DatabaseConnection) -> Result<u64> { let mut inserted = 0; for (id, name) in DEMO_ORGS { let stmt = Query::insert() .into_table(Org::Table) .columns([Org::Id, Org::Name]) .values_panic([id.into(), name.to_owned().into()]) .on_conflict(OnConflict::column(Org::Id).do_nothing().to_owned()) .to_owned(); inserted += db.execute(&stmt).await?.rows_affected(); } Ok(inserted)}Rows that depend on others reach for those constants. Demo users also use
fixed ids — user::ACME_AUTHOR is the author of Acme posts — and conflict on
the unique email:
pub const ACME_AUTHOR: Uuid = Uuid::from_u128(0x0000_0000_0000_0000_0000_0000_0000_ac01);
const DEMO: [(Uuid, Uuid, &str, &str); 5] = [ (ACME_AUTHOR, org::ACME, "Acme Author", "acme-user-1@example.test"), // ...];
// INSERT ... ON CONFLICT (email) DO NOTHING.Posts hang off the org and author constants. post::WELCOME is a stable id
for the first Acme article:
pub const WELCOME: Uuid = Uuid::from_u128(0x0000_0000_0000_0000_0000_0000_0000_b001);
const DEMO: [(Uuid, Uuid, Uuid, &str, &str); 3] = [ ( WELCOME, org::ACME, user::ACME_AUTHOR, "Welcome to Publish", "Getting started with nestrs and the Publish demo app.", ), // ...];
// INSERT ... ON CONFLICT (id) DO NOTHING.The runner
Section titled “The runner”seed::run calls each factory in dependency order and sums the inserts —
parents before children, so foreign keys resolve:
pub async fn run(db: &DatabaseConnection) -> Result<u64> { let mut inserted = 0; inserted += factories::org::seed(db).await?; inserted += factories::user::seed(db).await?; inserted += factories::post::seed(db).await?; Ok(inserted)}Run it
Section titled “Run it”nestrs run db seed # load demo data (idempotent)nestrs run db reset # fresh migrate, then seednestrs run db seed runs the crate’s seed binary, which reads
NESTRS_DATABASE__URL, runs seed::run, and prints how many rows
it inserted.
Because run is a plain function, tests call it directly. The crate’s e2e
migrates a throwaway database, seeds it, asserts rows landed, then seeds again
and asserts the second pass inserts zero — idempotence, verified.
Going further
Section titled “Going further”- Migrations — the schema the seed data fills.
- Database — entities and the service that reads them back.