mirror of
https://github.com/actix/examples
synced 2025-04-22 08:34:52 +02:00
feat: diesel-async usage example
This commit is contained in:
parent
df0dc549a5
commit
631c5f9472
@ -17,6 +17,7 @@ members = [
|
|||||||
"cors/backend",
|
"cors/backend",
|
||||||
"data-factory",
|
"data-factory",
|
||||||
"databases/diesel",
|
"databases/diesel",
|
||||||
|
"databases/diesel-async",
|
||||||
"databases/mongodb",
|
"databases/mongodb",
|
||||||
"databases/mysql",
|
"databases/mysql",
|
||||||
"databases/postgres",
|
"databases/postgres",
|
||||||
|
12
databases/diesel-async/Cargo.toml
Normal file
12
databases/diesel-async/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "db-diesel-async"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web.workspace = true
|
||||||
|
diesel = { version = "*", default-features = false, features = ["uuid"] }
|
||||||
|
diesel-async = { version = "*", features = ["postgres", "bb8", "async-connection-wrapper"] }
|
||||||
|
serde.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
|
dotenvy.workspace = true
|
108
databases/diesel-async/README.md
Normal file
108
databases/diesel-async/README.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# diesel
|
||||||
|
|
||||||
|
Basic integration of [Diesel-async](https://github.com/weiznich/diesel_async) using PostgreSQL for Actix Web.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Install PostgreSQL
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# on any OS
|
||||||
|
docker run -d --restart unless-stopped --name postgresql -e POSTGRES_USER=test-user -e POSTGRES_PASSWORD=password -p 5432:5432 -v postgres_data:/var/lib/postgresql/data postgres:alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
### Initialize PostgreSQL Database
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd databases/diesel-async
|
||||||
|
cargo install diesel_cli --no-default-features --features postgres
|
||||||
|
|
||||||
|
echo DATABASE_URL=postgres://test-user:password@localhost:5432/test_db > .env
|
||||||
|
diesel setup
|
||||||
|
diesel migration run
|
||||||
|
```
|
||||||
|
|
||||||
|
The database will now be created in your PostgreSQL instance.
|
||||||
|
```sh
|
||||||
|
docker exec -i postgresql psql -U test-user -c "\l"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Server
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd databases/diesel-async
|
||||||
|
cargo run
|
||||||
|
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Routes
|
||||||
|
|
||||||
|
#### `POST /item`
|
||||||
|
|
||||||
|
Inserts a new item into the PostgreSQL DB.
|
||||||
|
|
||||||
|
Provide a JSON payload with a name. Eg:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "name": "bill" }
|
||||||
|
```
|
||||||
|
|
||||||
|
On success, a response like the following is returned:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "01948982-67d0-7a55-b4b1-8b8b962d8c6b",
|
||||||
|
"name": "bill"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Client Examples</summary>
|
||||||
|
|
||||||
|
Using [HTTPie]:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
http POST localhost:8080/item name=bill
|
||||||
|
```
|
||||||
|
|
||||||
|
Using cURL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -S -X POST --header "Content-Type: application/json" --data '{"name":"bill"}' http://localhost:8080/item
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
#### `GET /item/{item_uid}`
|
||||||
|
|
||||||
|
Gets an item from the DB using its UID (returned from the insert request or taken from the DB directly). Returns a 404 when no item exists with that UID.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Client Examples</summary>
|
||||||
|
|
||||||
|
Using [HTTPie]:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
http localhost:8080/item/9e46baba-a001-4bb3-b4cf-4b3e5bab5e97
|
||||||
|
```
|
||||||
|
|
||||||
|
Using cURL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -S http://localhost:8080/item/9e46baba-a001-4bb3-b4cf-4b3e5bab5e97
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### Explore The PostgreSQL DB
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker exec -i postgresql psql -U test-user -d test_db -c "select * from public.items"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Other Databases
|
||||||
|
|
||||||
|
You can find a complete example of Diesel + PostgreSQL at: [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix)
|
||||||
|
|
||||||
|
[httpie]: https://httpie.io/cli
|
9
databases/diesel-async/diesel.toml
Normal file
9
databases/diesel-async/diesel.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
||||||
|
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||||
|
|
||||||
|
[migrations_directory]
|
||||||
|
dir = "/home/alex/CLionProjects/actix-with-async-diesel/migrations"
|
0
databases/diesel-async/migrations/.keep
Normal file
0
databases/diesel-async/migrations/.keep
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||||
|
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
@ -0,0 +1,36 @@
|
|||||||
|
-- This file was automatically created by Diesel to setup helper functions
|
||||||
|
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||||
|
-- changes will be added to existing projects as new migrations.
|
||||||
|
|
||||||
|
|
||||||
|
-- Sets up a trigger for the given table to automatically set a column called
|
||||||
|
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||||
|
-- in the modified columns)
|
||||||
|
--
|
||||||
|
-- # Example
|
||||||
|
--
|
||||||
|
-- ```sql
|
||||||
|
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||||
|
--
|
||||||
|
-- SELECT diesel_manage_updated_at('users');
|
||||||
|
-- ```
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
IF (
|
||||||
|
NEW IS DISTINCT FROM OLD AND
|
||||||
|
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||||
|
) THEN
|
||||||
|
NEW.updated_at := current_timestamp;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE IF EXISTS items;
|
@ -0,0 +1,6 @@
|
|||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE IF NOT EXISTS items
|
||||||
|
(
|
||||||
|
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL
|
||||||
|
);
|
53
databases/diesel-async/src/actions.rs
Normal file
53
databases/diesel-async/src/actions.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use diesel::prelude::*;
|
||||||
|
use uuid::{NoContext, Timestamp, Uuid};
|
||||||
|
|
||||||
|
use crate::models;
|
||||||
|
|
||||||
|
use diesel_async::AsyncPgConnection;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
type DbError = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
|
// /// Run query using Diesel to find item by uid and return it.
|
||||||
|
pub async fn find_item_by_uid(
|
||||||
|
conn: &mut AsyncPgConnection,
|
||||||
|
uid: Uuid,
|
||||||
|
) -> Result<Option<models::Item>, DbError> {
|
||||||
|
use super::schema::items::dsl::*;
|
||||||
|
|
||||||
|
let item = items
|
||||||
|
.filter(id.eq(uid))
|
||||||
|
.select(models::Item::as_select())
|
||||||
|
// execute the query via the provided
|
||||||
|
// async `diesel_async::RunQueryDsl`
|
||||||
|
.first::<models::Item>(conn)
|
||||||
|
.await
|
||||||
|
.optional()?;
|
||||||
|
|
||||||
|
Ok(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run query using Diesel to insert a new database row and return the result.
|
||||||
|
pub async fn insert_new_item(
|
||||||
|
conn: &mut AsyncPgConnection,
|
||||||
|
nm: &str, // prevent collision with `name` column imported inside the function
|
||||||
|
) -> Result<models::Item, DbError> {
|
||||||
|
// It is common when using Diesel with Actix Web to import schema-related
|
||||||
|
// modules inside a function's scope (rather than the normal module's scope)
|
||||||
|
// to prevent import collisions and namespace pollution.
|
||||||
|
use crate::schema::items::dsl::*;
|
||||||
|
|
||||||
|
let new_item = models::Item {
|
||||||
|
id: Uuid::new_v7(Timestamp::now(NoContext)),
|
||||||
|
name: nm.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let item = diesel::insert_into(items)
|
||||||
|
.values(&new_item)
|
||||||
|
.returning(models::Item::as_returning())
|
||||||
|
.get_result(conn)
|
||||||
|
.await
|
||||||
|
.expect("Error inserting person");
|
||||||
|
|
||||||
|
Ok(item)
|
||||||
|
}
|
94
databases/diesel-async/src/main.rs
Normal file
94
databases/diesel-async/src/main.rs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
|
||||||
|
use std::env::VarError;
|
||||||
|
|
||||||
|
use actix_web::{error, get, post, web, App, HttpResponse, HttpServer, Responder};
|
||||||
|
use diesel_async::pooled_connection::{bb8::Pool, AsyncDieselConnectionManager, PoolError};
|
||||||
|
use diesel_async::AsyncPgConnection;
|
||||||
|
use dotenvy::dotenv;
|
||||||
|
use std::{env, io};
|
||||||
|
use thiserror::Error as ThisError;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub mod actions;
|
||||||
|
pub mod models;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
|
type DbPool = Pool<AsyncPgConnection>;
|
||||||
|
|
||||||
|
/// Finds item by UID.
|
||||||
|
///
|
||||||
|
/// Extracts:
|
||||||
|
/// - the database pool handle from application data
|
||||||
|
/// - an item UID from the request path
|
||||||
|
#[get("/items/{item_id}")]
|
||||||
|
async fn get_item(
|
||||||
|
pool: web::Data<DbPool>,
|
||||||
|
item_uid: web::Path<Uuid>,
|
||||||
|
) -> actix_web::Result<impl Responder> {
|
||||||
|
let item_uid = item_uid.into_inner();
|
||||||
|
|
||||||
|
let mut conn = pool
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.expect("Couldn't get db connection from the pool");
|
||||||
|
|
||||||
|
let item = actions::find_item_by_uid(&mut conn, item_uid)
|
||||||
|
.await
|
||||||
|
// map diesel query errors to a 500 error response
|
||||||
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
|
|
||||||
|
Ok(match item {
|
||||||
|
// item was found; return 200 response with JSON formatted item object
|
||||||
|
Some(item) => HttpResponse::Ok().json(item),
|
||||||
|
|
||||||
|
// item was not found; return 404 response with error message
|
||||||
|
None => HttpResponse::NotFound().body(format!("No item found with UID: {item_uid}")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new item.
|
||||||
|
///
|
||||||
|
/// Extracts:
|
||||||
|
/// - the database pool handle from application data
|
||||||
|
/// - a JSON form containing new item info from the request body
|
||||||
|
#[post("/items")]
|
||||||
|
async fn add_item(
|
||||||
|
pool: web::Data<DbPool>,
|
||||||
|
form: web::Json<models::NewItem>,
|
||||||
|
) -> actix_web::Result<impl Responder> {
|
||||||
|
|
||||||
|
let mut conn = pool
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.expect("Couldn't get db connection from the pool");
|
||||||
|
|
||||||
|
let item = actions::insert_new_item(&mut conn, &form.name)
|
||||||
|
.await
|
||||||
|
// map diesel query errors to a 500 error response
|
||||||
|
.map_err(error::ErrorInternalServerError)?;
|
||||||
|
|
||||||
|
// item was added successfully; return 201 response with new item info
|
||||||
|
Ok(HttpResponse::Created().json(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> io::Result<()> {
|
||||||
|
dotenv().ok();
|
||||||
|
|
||||||
|
let db_url = env::var("DATABASE_URL").expect("Env var `DATABASE_URL` not set");
|
||||||
|
|
||||||
|
let mgr = AsyncDieselConnectionManager::<AsyncPgConnection>::new(db_url);
|
||||||
|
let pool = Pool::builder().build(mgr).await.unwrap();
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.app_data(web::Data::new(pool.clone()))
|
||||||
|
.service(add_item)
|
||||||
|
.service(get_item)
|
||||||
|
})
|
||||||
|
.bind(("127.0.0.1", 5000))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
25
databases/diesel-async/src/models.rs
Normal file
25
databases/diesel-async/src/models.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use super::schema::items;
|
||||||
|
|
||||||
|
/// Item details.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Queryable, Selectable, Insertable)]
|
||||||
|
#[diesel(table_name = items)]
|
||||||
|
pub struct Item {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New item details.
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
pub struct NewItem {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewItem {
|
||||||
|
/// Constructs new item details from name.
|
||||||
|
#[cfg(test)] // only needed in tests
|
||||||
|
pub fn new(name: impl Into<String>) -> Self {
|
||||||
|
Self { name: name.into(), ..Default::default() }
|
||||||
|
}
|
||||||
|
}
|
8
databases/diesel-async/src/schema.rs
Normal file
8
databases/diesel-async/src/schema.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
items (id) {
|
||||||
|
id -> Uuid,
|
||||||
|
name -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user