Thursday, April 25, 2024
HomeJavaAn HTTP API with Rust

An HTTP API with Rust


For a very long time, I used the MySQL database for my demos. However I began to learn a variety of great things about PostgreSQL and determined to change. I wanted an asynchronous library appropriate with Tokio: it’s precisely what the tokio_postgres crate does.

The issue with the crate is that it creates direct connections to the database. I looked for a connection pool crate and stumbled upon deadpool (sic):

Deadpool is a lifeless easy async pool for connections and objects of any kind.

Deadpool gives two distinct implementations:

  • An unmanaged pool: the developer has full management – and accountability – over the pooled objects’ lifecycle
  • A managed pool: the crate creates and recycles objects as wanted

Extra specialised implementations of the latter cater to totally different databases or “drivers”, e.g. Redis and…​ tokio-postgres. One can configure Deadpool straight or defer to the config crate it helps. The latter crate permits a number of options for configuration:

Config organizes hierarchical or layered configurations for Rust purposes.

Config allows you to set a set of default parameters after which lengthen them by way of merging in configuration from quite a lot of sources:

  • Setting variables
  • String literals in well-known codecs
  • One other Config occasion
  • Recordsdata: TOML, JSON, YAML, INI, RON, JSON5, and customized ones outlined with Format trait
  • Guide, programmatic override (by way of a .set methodology on the Config occasion)

Moreover, Config helps:

  • Reside watching and re-reading of configuration recordsdata
  • Deep entry into the merged configuration by way of a path syntax
  • Deserialization by way of serde of the configuration or any subset outlined by way of a path

To create the bottom configuration, one must create a devoted construction and use the crate:

#[derive(Deserialize)]                                      (1)
struct ConfigBuilder {
    postgres: deadpool_postgres::Config,                     (2)
}

impl ConfigBuilder {
    async fn from_env() -> Outcome<Self, ConfigError> {       (3)
        Config::builder()
            .add_source(
                Setting::with_prefix("POSTGRES")         (4)
                    .separator("_")                         (4)
                    .keep_prefix(true)                       (5)
                    .try_parsing(true),
            )
            .construct()?
            .try_deserialize()
    }
}

let cfg_builder = ConfigBuilder::from_env().await.unwrap(); (6)

1 The Deserialize macro is necessary
2 The sector should match the surroundings prefix, see beneath
3 The perform is async and returns a Outcome
4 Learn from surroundings variables whose title begins with POSTGRES_
5 Hold the prefix within the configuration map
6 Take pleasure in!

Notice that surroundings variables ought to conform to what Deadpool’s Config expects. Right here’s my configuration in Docker Compose:

POSTGRES_HOST

"postgres"

POSTGRES_PORT

5432

POSTGRES_USER

"postgres"

POSTGRES_PASSWORD

"root"

POSTGRES_DBNAME

"app"

As soon as we’ve got initialized the configuration, we will create the pool:

struct AppState {
    pool: Pool,                                                     (1)
}

impl AppState {
    async fn create() -> Arc<AppState> {                            (2)
        let cfg_builder = ConfigBuilder::from_env().await.unwrap();  (3)
        let pool = cfg_builder                                      (4)
            .postgres
            .create_pool(
                Some(deadpool_postgres::Runtime::Tokio1),
                tokio_postgres::NoTls,
            )
            .unwrap();
        Arc::new(AppState { pool })                                 (2)
    }
}

1 Wrap the pool in a customized struct
2 Wrap the struct in an Arc to go it inside an axum State (see above)
3 Get the configuration
4 Create the pool

Then, we will go the pool to the routing features:

let app_state = AppState::create().await;                           (1)
let app = Router::new()
    .route("/individuals", get(get_all))
    .with_state(Arc::clone(&app_state));                            (2)

async fn get_all(State(state): State<Arc<AppState>>) -> Response {
    let consumer = state.pool.get().await.unwrap();                   (3)
    let rows = consumer
        .question("SELECT id, first_name, last_name FROM particular person", &[])  (4)
        .await                                                      (5)
        .unwrap();
    //                                                              (6)
}

1 Create the state
2 Cross the state to the routing features
3 Get the pool out of the state, and get the consumer out of the pool
4 Create the question
5 Execute it
6 Learn the row to populate the Response

The final step is to implement the transformation from a Row to a Particular person. We will do it with the From trait.

impl From<&Row> for Particular person {
    fn from(row: &Row) -> Self {
        let first_name: String = row.get("first_name");
        let last_name: String = row.get("last_name");
        Particular person {
            first_name,
            last_name,
        }
    }
}

let particular person = row.into();

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments