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.
1. Turn the feature back on
Section titled “1. Turn the feature back on”A --db none app pins loco-rs with default features off:
# Cargo.toml — beforeloco-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.
2. Create the migration crate
Section titled “2. Create the migration crate”It is a sibling crate at migration/, referenced by path. Two files:
[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"]#![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) ] }}3. Add the models module
Section titled “3. Add the models module”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/.
4. Wire Hooks and the binaries
Section titled “4. Wire Hooks and the binaries”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.rsuse 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;5. Configure the connection
Section titled “5. Configure the connection”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:
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: falsedatabase: uri: <%= get_env(name="DATABASE_URL") %> auto_migrate: false dangerously_truncate: false dangerously_recreate: falseUse 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.
6. Verify
Section titled “6. Verify”cargo build # the Hooks/Migrator wiring compilescargo loco db status # the connection workscargo loco generate model post title:string! content:textcargo loco db migratecargo loco db entities # needs: cargo install sea-orm-clicargo loco startIf generate model reports that it cannot inject into migration/src/lib.rs, the anchor comment from step 2 is missing or was reformatted.
- Add a model — the field syntax and the normal workflow from here on
- Query data
- Multi-database — if you need more than one connection