Skip to content
★ GitHubGet started

Add a database to an existing app

Goal: add a database to an app you generated without one.

Choosing no database at loco new is not a small switch — it turns off the with-db feature, which removes AppContext::db, the migration crate, the models module, and two required Hooks methods. No generator reverses it, so this page is the procedure. Budget half an hour.

A --db none app pins loco-rs with default features off:

# Cargo.toml — before
loco-rs = { workspace = true, features = ["cli"] }

with-db is a default feature, so the fix is to stop disabling defaults. In [workspace.dependencies], drop default-features = false, then add the database dependencies a db app ships with:

# Cargo.toml — after
[workspace.dependencies]
loco-rs = { version = "1.1" } # no `default-features = false`
[dependencies]
loco-rs = { workspace = true }
migration = { path = "migration" }
sea-orm = { version = "2.0", features = [
"sqlx-sqlite",
"sqlx-postgres",
"runtime-tokio-rustls",
"macros",
] }
chrono = { version = "0.4" }
validator = { version = "0.20" }
uuid = { version = "1.6", features = ["v4"] }

Add ts-rs = { version = "12", features = ["chrono-impl", "serde-compat"] } as well if you want the typed DTO bindings — see Build a typed React SPA.

It is a sibling crate at migration/, referenced by path. Two files:

migration/Cargo.toml
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
loco-rs = { workspace = true }
[dependencies.sea-orm-migration]
version = "2.0"
features = ["runtime-tokio-rustls"]
migration/src/lib.rs
#![allow(elided_lifetimes_in_paths)]
#![allow(clippy::wildcard_imports)]
pub use sea_orm_migration::prelude::*;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
// inject-above (do not remove this comment)
]
}
}
src/models/
├── mod.rs # pub mod _entities;
└── _entities/
├── mod.rs # pub mod prelude;
└── prelude.rs # (empty for now)

Then declare it in src/lib.rs:

pub mod models;
pub mod dtos; // only if you added ts-rs in step 1

_entities/ is generated code — cargo loco db entities rewrites it from the live schema. Your own model logic goes in src/models/<name>.rs next to it, never inside _entities/.

This is where the compiler errors the archetype hits come from, and they are all mechanical.

In src/app.rs, boot gains the Migrator type parameter:

use migration::Migrator;
async fn boot(mode: StartMode, environment: &Environment, config: Config) -> Result<BootResult> {
create_app::<Self, Migrator>(mode, environment, config).await
}

with-db also makes two more Hooks methods required — they have no default bodies, so impl Hooks for App will not compile until both exist:

use std::path::Path;
async fn truncate(_ctx: &AppContext) -> Result<()> {
Ok(())
}
async fn seed(_ctx: &AppContext, _base: &Path) -> Result<()> {
Ok(())
}

Empty bodies are fine to start; fill them in when you need them (see Seed data).

Both binaries take the same parameter:

// src/bin/main.rs and src/bin/tool.rs
use migration::Migrator;
#[tokio::main]
async fn main() -> loco_rs::Result<()> {
cli::main::<App, Migrator>().await
}

And tests/mod.rs gains its models module once you have model tests:

mod models;

Add a database: block to every environment config — config/development.yaml, config/test.yaml, and config/production.yaml. Production takes no defaults, so it must read from the environment:

config/development.yaml
database:
uri: <%= get_env(name="DATABASE_URL", default="sqlite://myapp_development.sqlite?mode=rwc") %>
enable_logging: false
connect_timeout: 500
idle_timeout: 500
min_connections: 1
max_connections: 1
auto_migrate: true
dangerously_truncate: false
dangerously_recreate: false
config/production.yaml
database:
uri: <%= get_env(name="DATABASE_URL") %>
auto_migrate: false
dangerously_truncate: false
dangerously_recreate: false

Use sqlite://…?mode=rwc for SQLite or postgres://user:pass@host:5432/dbname for Postgres. Never set dangerously_truncate or dangerously_recreate outside development and test.

Terminal window
cargo build # the Hooks/Migrator wiring compiles
cargo loco db status # the connection works
cargo loco generate model post title:string! content:text
cargo loco db migrate
cargo loco db entities # needs: cargo install sea-orm-cli
cargo loco start

If generate model reports that it cannot inject into migration/src/lib.rs, the anchor comment from step 2 is missing or was reformatted.