lilydocs
HTTP APIWebSocketConsumer
Data3 min read

PostgreSQL

Build your data layer with Lily-managed connection pools and Diesel queries.

lily_postgresql manages the pool, TLS, configuration, and connection lifecycle. Schema, entities, and queries belong to Diesel. Using a repository is optional.

Dependencies and connection#

A running PostgreSQL instance and a pre-existing schema are required. Add the postgresql feature to the lilyrs dependency from the first application; it selects single mode. Also declare the Diesel dependency for the repository example:

Cargo.toml additions
lilyrs = { version = "0.1.0", features = ["http-api", "postgresql"] }
diesel = { version = "=2.3.11", features = ["postgres"] }
TOML
[postgresql]
mode = "single"
connection_string = "postgresql://lily:lily@127.0.0.1:5432/lily"

[postgresql.tls]
mode = "disable"

[postgresql.pool]
max_size = 10
acquire_timeout_secs = 5
transaction_cleanup_timeout_secs = 5

This connection is for local development. Production configuration validation requires verify-full TLS; disable is rejected. In production, use secret references and appropriate CA/hostname verification. PostgreSQL settings belong under [postgresql]; [database] is for MongoDB.

Choose a data-access path#

Path Use
PgDatabaseService Direct Diesel access through with_connection(callback, cancellation)
PgRepository derive Generated CRUD and *_in methods
PgDbContext Share transactions between workflows and repositories within the same request

The default single mode registers Singleton PgDatabaseService and Scoped PgDbContext. A direct callback receives (&mut AsyncPgConnection, Option<ExecutionCancellation>) and returns Box::pin(async move { ... }). Supported Diesel versions are re-exported by the crate.

Optional repository#

pg_repository.rs
use std::sync::Arc;
use diesel::prelude::*;
use lilyrs::http_api::Injectable;
use lilyrs::http_api::ServiceTrait;
use lilyrs::postgresql::{PgDatabaseService, PgRepository};

diesel::table! {
    users (id) {
        id -> BigInt,
        name -> Text,
    }
}

#[derive(Clone, Queryable, Selectable, Identifiable, Insertable, AsChangeset)]
#[diesel(table_name = users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
struct User {
    #[diesel(skip_insertion)]
    #[diesel(skip_update)]
    id: i64,
    name: String,
}

#[derive(Default, Injectable, PgRepository)]
#[service(lifetime = "Singleton")]
#[pg(entity = User)]
struct UserRepository {
    #[inject]
    database: Arc<PgDatabaseService>,
}
impl ServiceTrait for UserRepository {}

PgRepository alone does not register a DI service; the example's Injectable and ServiceTrait are also required. Generated methods include create, create_many, find_by_id, find_by_ids, update, delete_by_id, count, exists, and their *_in forms.

Migrations and transactions#

run_pending_migrations is an explicit startup/deployment operation. Do not run DDL for each request. The next page shows a complete transaction spanning a controller and two repository calls.

Multiple databases#

In the facade, replace postgresql with postgresql-factory: features = ["http-api", "postgresql-factory"]. Access named databases through lilyrs::postgresql::PgFactory. The base postgresql feature enables single mode, so do not combine it with factory mode; feature unification with other workspace members can also cause this conflict. Obtain a database with get(name) on the injected factory. Create a named PgDbContext in an application-owned scoped wrapper, and forward wrapper cleanup to the context. Adding a feature to single-mode code is not sufficient; the configuration topology and initializer must also follow the factory model.

Sources for this page v0.1.0

This guide is based on the reviewed framework commit.

QUICK START
Documentation
Source code