Autumn ships a first-class seed convention so a freshly-migrated database can be populated with representative data in a single command.
autumn migrate && autumn seed
The convention
Seed code lives in src/bin/seed.rs — an ordinary Cargo binary that receives
a database connection through [autumn_web::seed::SeedContext]. No special
DSL, no template language, and no duplicated connection wiring: seed code uses
the same #[model] / #[repository] types the application uses, so the
compiler keeps everything in sync.
The binary is discovered by autumn seed through the Cargo binary target
named seed.
Quick start
1. Add the seed feature to autumn-web
# Cargo.toml
[dependencies]
autumn-web = { version = "0.4", features = ["seed"] }
[[bin]]
name = "seed"
path = "src/bin/seed.rs"
Or scaffold it automatically when creating a new project:
autumn new my-app --with-seed
2. Write src/bin/seed.rs
use autumn_web::seed::SeedContext;
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use my_app::schema::posts;
#[derive(Insertable)]
#[diesel(table_name = posts)]
struct NewPost<'a> {
title: &'a str,
body: &'a str,
}
#[tokio::main]
async fn main() {
let ctx = SeedContext::build().expect("seed context");
println!("Seeding ({})...", ctx.profile());
let mut db = ctx.conn().await.expect("db connection");
// Idempotency guard — skip if the table already has data.
let count: i64 = posts::table.count().get_result(&mut *db).await.unwrap_or(0);
if count > 0 {
println!("Already seeded; skipping.");
return;
}
diesel::insert_into(posts::table)
.values(&[
NewPost { title: "Hello, world!", body: "My first post." },
NewPost { title: "Getting started", body: "Autumn makes it easy." },
])
.execute(&mut *db)
.await
.expect("insert failed");
println!("Seeded 2 posts.");
}
3. Run
# Run migrations first (autumn seed will error if pending migrations exist)
autumn migrate
# Seed the database
autumn seed
# Use a non-default profile
autumn seed --profile demo
How it works
autumn seed does four things:
-
Checks that
src/bin/seed.rsexists. If it does not, you get a clear error:Code✗ no seed binary found; create `src/bin/seed.rs` or run `autumn generate seed` -
Checks for pending migrations (when the
dieselCLI is available). Seeds run after migrations; if any are pending, you see:Code✗ pending migrations detected; run `autumn migrate` before `autumn seed` -
Sets the profile via the
AUTUMN_ENVenvironment variable (default:dev). Your seed binary readsctx.profile()to branch on environment. -
Delegates to
cargo run --bin seed. All Cargo flags such as--packagework:Shellautumn seed --profile demo --package my-workspace-member
Profile-aware seeding
SeedContext::build() reads the profile from AUTUMN_ENV (or AUTUMN_PROFILE
as a legacy alias), which autumn seed sets automatically. Use ctx.profile()
to vary the seed data between environments:
let items: Vec<_> = match ctx.profile() {
"demo" => demo_items(),
_ => dev_items(),
};
Idempotency pattern
Autumn does not enforce idempotency — that is your responsibility. Two common patterns:
Count-based guard (simplest)
let count: i64 = my_table::table.count().get_result(&mut *db).await.unwrap_or(0);
if count > 0 {
println!("Already seeded; skipping.");
return;
}
Re-running inserts nothing if the table already has rows.
Upsert-by-natural-key
If your table has a unique index on a natural key (e.g. slug), use
ON CONFLICT DO NOTHING:
diesel::insert_into(posts::table)
.values(&seed_data)
.on_conflict(posts::slug)
.do_nothing()
.execute(&mut *db)
.await?;
Re-running skips rows whose slug already exists.
SeedContext API reference
/// Build a seed context from environment + autumn.toml.
pub fn build() -> Result<SeedContext, SeedContextError>
/// Active profile (e.g. "dev", "demo", "test").
pub fn profile(&self) -> &str
/// Acquire a pooled connection.
pub async fn conn(&self) -> Result<Object<AsyncPgConnection>, SeedContextError>
Object<AsyncPgConnection> implements DerefMut to AsyncPgConnection, so
pass it to diesel-async query methods as &mut *conn.
Example: examples/todo-app
The canonical todo-app example ships a complete seed at
examples/todo-app/src/bin/seed.rs. Its idempotency guard uses the
count-based pattern: if any todos already exist, the seed exits early.
cd examples/todo-app
autumn migrate && autumn seed && autumn dev
# → localhost:3000 shows five pre-populated todos
Out of scope
- Test fixtures — use
autumn_web::testhelpers for integration test data. - YAML/JSON/CSV loaders — author a thin loader inside your seed binary if you want declarative fixtures.
- Faker / factory libraries — compose with
fake,proptest,rand, or whatever you prefer; Autumn owns the runner, not the data generation. autumn generate seed— tracked in #493 follow-up work.
See also
docs/guide/getting-started.md— includesautumn seedin the quickstart flowdocs/guide/generators.md—autumn generate model/ scaffold