Connecting a Database

Because educated guesses are overrated

Our app needs a way to store secrets before it can send them to another requester. We could store everything in a shared state memory, but when the thruster server shutdown we'd lose everything, and it would be hard to scale to more than one instance reasonably (ain't nobody got time to implement Paxos.)

So we need a database! For this example, we'll be using Postgres along with sqlx. Postgres is a pretty standard choice, but why sqlx as our rust library? Well, it's quick to set up and has a nice compile time query checker if you have a database running locally.

Start by making a new schema.sql file in the root folder.

CREATE TABLE IF NOT EXISTS secrets (
    secret_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    encoded_secret VARCHAR(1024) NOT NULL,
    salt VARCHAR(22) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT now(),
    expires_at TIMESTAMPTZ
);

If we were making a more "production" ready app, of course we'd want to use proper migrations rather than a single "schema" file. diesel is a great tool for running migrations. It even has a proper ORM if you want the extra safety around your data model!

We'll need to run this migration when the app starts (note that we only create the table if it doesn't already exist -- so the app can be run multiple times without clearing the data out of the table or failing because of conflicts!)

Add the following to your Cargo.toml under the "dependencies" section:

sqlx = { version = "0.6", features = ["runtime-tokio-native-tls", "postgres"] }

Now we need to actually apply the schema when our app runs. Make your main function look like this:

#[tokio::main]
async fn main() {
    env_logger::init();
    info!("Starting server...");

    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect("postgres://postgres:password@localhost/pigeon")
        .await
        .expect("Could not create postgres connection pool");

    pool.execute(include_str!("../schema.sql"))
        .await
        .expect("Could not create schema in database");

    let app = App::<HyperRequest, Ctx, ServerConfig>::create(generate_context, ServerConfig {})
        .get("/hello", m![hello]);

    let server = HyperServer::new(app);
    server.build("0.0.0.0", 4321).await;
}

You'll note that we add a pool, execute the schema on the pool (i.e. we run the above sql code,) and then we... don't really do anything with the pool -- yet!

Before we try running this, we'll need to, you know, actually have a postgres instance running locally! Docker is a great way to do this without much fuss. If you don't already have Docker installed, do so now, then run

docker run -d \
    -e POSTGRES_PASSWORD=password \
    -e POSTGRES_DB=pigeon \
    -p 5432:5432
    --name=pigeon-dev \
    postgres

This starts postgres in a container and detaches it, letting you run the program,

cargo run

Which should run nice an easy! You did it, you connected to a database!!

Last updated