#axum #sqlite

axum-sqlx-tx

Request-scoped SQLx transactions for axum

9 releases (5 breaking)

0.6.0 Sep 15, 2023
0.5.0 Feb 8, 2023
0.4.1 Feb 8, 2023
0.4.0 Sep 20, 2022
0.1.2 Mar 4, 2022

#510 in Database interfaces

Download history 25/week @ 2023-06-03 11/week @ 2023-06-10 51/week @ 2023-06-17 37/week @ 2023-06-24 34/week @ 2023-07-01 16/week @ 2023-07-08 24/week @ 2023-07-15 38/week @ 2023-07-22 22/week @ 2023-07-29 44/week @ 2023-08-05 50/week @ 2023-08-12 36/week @ 2023-08-19 45/week @ 2023-08-26 45/week @ 2023-09-02 78/week @ 2023-09-09 52/week @ 2023-09-16

227 downloads per month
Used in aide

MIT license

31KB
437 lines

axum-sqlx-tx

Request-bound SQLx transactions for axum.

Summary

axum-sqlx-tx provides an axum extractor for obtaining a request-bound transaction. The transaction begins the first time the extractor is used, and is stored with the request for use by other middleware/handlers. The transaction is resolved depending on the status code of the response – successful (2XX) responses will commit the transaction, otherwise it will be rolled back.

See the crate documentation for more information and examples.


lib.rs:

Request-bound SQLx transactions for axum.

[Tx] is an axum extractor for obtaining a transaction that's bound to the HTTP request. A transaction begins the first time the extractor is used for a request, and is then stored in request extensions for use by other middleware/handlers. The transaction is resolved depending on the status code of the eventual response – successful (HTTP 2XX or 3XX) responses will cause the transaction to be committed, otherwise it will be rolled back.

This behaviour is often a sensible default, and using the extractor (e.g. rather than directly using sqlx::Transactions) means you can't forget to commit the transactions!

Usage

To use the [Tx] extractor, you must first add Layer to your app:

let pool = /* any sqlx::Pool */
let app = axum::Router::new()
    // .route(...)s
    .layer(axum_sqlx_tx::Layer::new(pool));

You can then simply add [Tx] as an argument to your handlers:

use axum_sqlx_tx::Tx;
use sqlx::Sqlite;

async fn create_user(mut tx: Tx<Sqlite>, /* ... */) {
    // `&mut Tx` implements `sqlx::Executor`
    let user = sqlx::query("INSERT INTO users (...) VALUES (...)")
        .fetch_one(&mut tx)
        .await
        .unwrap();

    // `Tx` also implements `Deref<Target = sqlx::Transaction>` and `DerefMut`
    use sqlx::Acquire;
    let inner = tx.begin().await.unwrap();
    /* ... */
}

If you forget to add the middleware you'll get Error::MissingExtension (internal server error) when using the extractor. You'll also get an error (Error::OverlappingExtractors) if you have multiple Tx arguments in a single handler, or call Tx::from_request multiple times in a single middleware.

Error handling

axum requires that middleware do not return errors, and that the errors returned by extractors implement IntoResponse. By default, Error is used by Layer and [Tx] to convert errors into HTTP 500 responses, with the error's Display value as the response body, however it's generally not a good practice to return internal error details to clients!

To make it easier to customise error handling, both Layer and [Tx] have a second generic type parameter, E, that can be used to override the error type that will be used to convert the response.

use axum::response::IntoResponse;
use axum_sqlx_tx::Tx;
use sqlx::Sqlite;

struct MyError(axum_sqlx_tx::Error);

// Errors must implement From<axum_sqlx_tx::Error>
impl From<axum_sqlx_tx::Error> for MyError {
    fn from(error: axum_sqlx_tx::Error) -> Self {
        Self(error)
    }
}

// Errors must implement IntoResponse
impl IntoResponse for MyError {
    fn into_response(self) -> axum::response::Response {
        // note that you would probably want to log the error or something
        (http::StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response()
    }
}

// Change the layer error type
let app = axum::Router::new()
    // .route(...)s
    .layer(axum_sqlx_tx::Layer::new_with_error::<MyError>(pool));

// Change the extractor error type
async fn create_user(mut tx: Tx<Sqlite, MyError>, /* ... */) {
    /* ... */
}

Examples

See examples/ in the repo for more examples.

Dependencies

~8–24MB
~386K SLoC