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.
1. Prerequisites
Section titled “1. Prerequisites”Enable the db and orm features in your Cargo.toml.
[dependencies]nestforge = { version = "1", features = ["db", "orm"] }2. Managing Migrations
Section titled “2. Managing Migrations”NestForge includes a built-in migration system that allows you to version-control your database schema.
-
Initialize Migrations
Terminal window nestforge db init -
Generate a Migration
Terminal window nestforge db generate create_users_table -
Define your Schema Edit the generated
.sqlfile in themigrations/directory:CREATE TABLE users (id BIGSERIAL PRIMARY KEY,name TEXT NOT NULL,email TEXT NOT NULL UNIQUE); -
Apply Migrations Ensure your
DATABASE_URLis set in your.envfile, then run:Terminal window nestforge db migrate
3. Connecting the Database
Section titled “3. Connecting the Database”In your AppModule, you should register the Db provider. This makes the database connection pool available for injection throughout your app.
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;4. Building a DB Service
Section titled “4. Building a DB Service”Now, update your service to use the injected Db instance.
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) }}5. Registering the Factory
Section titled “5. Registering the Factory”Since the UsersService now requires Db in its constructor, we register it using a factory.
#[module( controllers = [UsersController], providers = [ Provider::factory(|container| { let db = container.resolve::<Db>()?; Ok(UsersService::new(db)) }) ], exports = [UsersService])]pub struct UsersModule;Pro Tip: Transactions
Section titled “Pro Tip: Transactions”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?;