Databases
While Rocket doesn’t have built-in support for databases yet, you can combine a few external libraries to get native-feeling access to databases in a Rocket application. Let’s take a look at how we might integrate Rocket with two common database libraries: diesel, a type-safe ORM and query builder, and r2d2, a library for connection pooling.
Our approach will be to have Rocket manage a pool of database connections using managed state and then implement a request guard that retrieves one connection. This will allow us to get access to the database in a handler by simply adding a DbConn
argument:
#[get("/users")]
fn handler(conn: DbConn) { ... }
Dependencies
To get started, we need to depend on the diesel
and r2d2
crates. For detailed information on how to use Diesel, please see the Diesel getting started guide. For this example, we use the following dependencies:
[dependencies]
rocket = "0.3.17"
rocket_codegen = "0.3.17"
diesel = { version = "<= 1.2", features = ["sqlite", "r2d2"] }
Your diesel
dependency information may differ. The crates are imported as well:
extern crate rocket;
#[macro_use] extern crate diesel;
Managed Pool
The first step is to initialize a pool of database connections. The init_pool
function below uses r2d2
to create a new pool of database connections. Diesel advocates for using a DATABASE_URL
environment variable to set the database URL, and we use the same convention here. Excepting the long-winded types, the code is fairly straightforward: the DATABASE_URL
environment variable is stored in the DATABASE_URL
static, and an r2d2::Pool
is created using the default configuration parameters and a Diesel SqliteConnection
ConnectionManager
.
use diesel::sqlite::SqliteConnection;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
// An alias to the type for a pool of Diesel SQLite connections.
type SqlitePool = Pool<ConnectionManager<SqliteConnection>>;
// The URL to the database, set via the `DATABASE_URL` environment variable.
static DATABASE_URL: &'static str = env!("DATABASE_URL");
/// Initializes a database pool.
fn init_pool() -> SqlitePool {
let manager = ConnectionManager::<SqliteConnection>::new(DATABASE_URL);
Pool::new(manager).expect("db pool")
}
We then use managed state to have Rocket manage the pool for us:
fn main() {
rocket::ignite()
.manage(init_pool())
.launch();
}
Connection Guard
The second and final step is to implement a request guard that retrieves a single connection from the managed connection pool. We create a new type, DbConn
, that wraps an r2d2
pooled connection. We then implement FromRequest
for DbConn
so that we can use it as a request guard. Finally, we implement Deref
with a target of SqliteConnection
so that we can transparently use an &*DbConn
as an &SqliteConnection
.
use std::ops::Deref;
use rocket::http::Status;
use rocket::request::{self, FromRequest};
use rocket::{Request, State, Outcome};
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
// Connection request guard type: a wrapper around an r2d2 pooled connection.
pub struct DbConn(pub PooledConnection<ConnectionManager<SqliteConnection>>);
/// Attempts to retrieve a single connection from the managed database pool. If
/// no pool is currently managed, fails with an `InternalServerError` status. If
/// no connections are available, fails with a `ServiceUnavailable` status.
impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let pool = request.guard::<State<SqlitePool>>()?;
match pool.get() {
Ok(conn) => Outcome::Success(DbConn(conn)),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ()))
}
}
}
// For the convenience of using an &DbConn as an &SqliteConnection.
impl Deref for DbConn {
type Target = SqliteConnection;
fn deref(&self) -> &Self::Target {
&self.0
}
}
Usage
With these two pieces in place, we can use DbConn
as a request guard in any handler or other request guard implementation, giving our application access to a database. As a simple example, we might write a route that returns a JSON array of some Task
structures that are fetched from a database:
#[get("/tasks")]
fn get_tasks(conn: DbConn) -> QueryResult<Json<Vec<Task>>> {
all_tasks.order(tasks::id.desc())
.load::<Task>(&*conn)
.map(|tasks| Json(tasks))
}