Overview
Corvis does not tie you to a database. Data access is just another provider: you register it in a module, inject it where you need it, and its lifecycle (connect on startup, disconnect on shutdown) hooks into the lifecycle events. You have two paths.
Built-in option: corvis-orm
The workspace includes corvis-orm, an ORM on top of sqlx for PostgreSQL and SQLite that is already prepared for dependency injection:
OrmModule::for_root(...)/for_root_async(...)registerOrmService, which connects inon_module_initand disconnects inon_module_destroy.- Models are structs with
#[derive(Model)]; they can also be generated from an existing file (it is compatible withschema.prisma). OrmExceptionFiltertranslates database errors into HTTP responses (409for a duplicate key,404for a missing record).
examples/blog-orm and examples/tertulia use it. The full guide is in corvis-orm.
Any other crate, as a provider
If you prefer sqlx directly, another SQL client, or a database that corvis-orm does not support, register the client or the pool with a factory provider. The factory is async and can read other providers, such as ConfigService:
use corvis::prelude::*;
use sqlx::PgPool;
#[module(
imports = [ConfigModule::for_root(ConfigModuleOptions { is_global: true, ..Default::default() })],
providers = [
Provider::use_factory(Token::of::<PgPool>(), |ctx| async move {
let config = ctx.get::<ConfigService>().await?;
let url: String = config.try_get("DATABASE_URL")?;
Ok(PgPool::connect(&url).await?)
}),
UsersRepository,
],
exports = [Token::of::<PgPool>()],
)]
pub struct DatabaseModule;
#[injectable]
pub struct UsersRepository {
pool: Arc<PgPool>,
}
impl UsersRepository {
pub async fn count(&self) -> Result<i64, sqlx::Error> {
sqlx::query_scalar("SELECT count(*) FROM users").fetch_one(&*self.pool).await
}
}Key points:
- The factory returns
corvis::Result<T>, so?works with any error that converts intoanyhow::Error. If it fails, the application does not start and the message says which provider could not be created. Token::of::<PgPool>()registers the pool by its type, and consumers request it asArc<PgPool>. If you prefer a string token, useProvider::use_factory("PG_POOL", ...)and#[inject("PG_POOL")] pool: Arc<PgPool>.- Export the token if other modules will use it, or mark the module as global.
sqlx(or whichever crate you use) is a dependency of your application, not of Corvis.
Factories, tokens and async providers are explained in Custom providers and Async providers.
Database errors in handlers
Corvis converts any error that implements std::error::Error into an HttpException with status 500, so ? works in a handler that returns HttpResult<T>. For more precise responses, map the errors yourself or register an exception filter that recognizes them with exception.downcast_cause::<YourError>(), which is what OrmExceptionFilter does.
Integrations that do not exist
There are no integration modules for TypeORM, Drizzle, MongoDB, MikroORM or Sequelize: they are Node.js libraries. The pages in this section with those names say so and suggest the Rust alternative.