Skip to content

Database (SQL)

Connecting to a SQL database, running migrations, and building DB-backed services.

NestForge provides a robust SQL layer built on top of sqlx. This guide walks you through setting up a database, managing migrations, and building services that interact with your data.

Enable the db and orm features in your Cargo.toml.

[dependencies]
nestforge = { version = "1", features = ["db", "orm"] }

NestForge includes a built-in migration system that allows you to version-control your database schema.

  1. Initialize Migrations

    Terminal window
    nestforge db init
  2. Generate a Migration

    Terminal window
    nestforge db generate create_users_table
  3. Define your Schema Edit the generated .sql file in the migrations/ directory:

    CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE
    );
  4. Apply Migrations Ensure your DATABASE_URL is set in your .env file, then run:

    Terminal window
    nestforge db migrate

In your AppModule, you should register the Db provider. This makes the database connection pool available for injection throughout your app.

src/app_module.rs
use nestforge::{module, Db, DbConfig};
fn connect_db() -> anyhow::Result<Db> {
// You can load this from config or env
let url = "postgres://user:pass@localhost/db";
Ok(Db::connect_lazy(DbConfig::new(url))?)
}
#[module(
imports = [UsersModule],
providers = [connect_db()?],
exports = [Db]
)]
pub struct AppModule;

Now, update your service to use the injected Db instance.

src/users/services/users_service.rs
use nestforge::Db;
use crate::users::dto::{UserDto, CreateUserDto};
pub struct UsersService {
db: Db,
}
impl UsersService {
pub fn new(db: Db) -> Self {
Self { db }
}
pub async fn find_all(&self) -> anyhow::Result<Vec<UserDto>> {
let rows = self.db
.fetch_all("SELECT id, name, email FROM users")
.await?;
// Map rows to DTOs...
Ok(rows)
}
pub async fn create(&self, dto: CreateUserDto) -> anyhow::Result<UserDto> {
let row = self.db
.fetch_one(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email",
&[&dto.name, &dto.email]
)
.await?;
Ok(row)
}
}

Since the UsersService now requires Db in its constructor, we register it using a factory.

src/users/mod.rs
#[module(
controllers = [UsersController],
providers = [
Provider::factory(|container| {
let db = container.resolve::<Db>()?;
Ok(UsersService::new(db))
})
],
exports = [UsersService]
)]
pub struct UsersModule;

When you need to perform multiple operations atomically, use the transaction API:

let mut tx = self.db.begin().await?;
tx.execute("INSERT INTO logs ...", &[...]).await?;
tx.execute("UPDATE users ...", &[...]).await?;
tx.commit().await?;