Rust REST APIとDBをSQLxで接続する
概要
今回は英語で記事を書いていきます。
Today we're going to connect an existing Actix Web REST API and a Postgres DB with the SQLx Rust crate. This tutorial assumes you have Rust installed already.
To learn how to setup an Actix Web REST API, check out my previous article: Fast REST API with Rust and Actix Web.
Before we begin, make sure you have your DATABASE_URL set in your .env file. An example can be seen in the .env.example file.
First we need to add our dependencies:
> cargo add dotenv sqlx --features="sqlx/postgres, sqlx/runtime-async-std, sqlx/tls-rustls"
Now, in our /src/main.rs we need to create our Postgres connection pool and share it with our routes:
2. use dotenv::dotenv;
3. use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
8. // information shared with each route
9. struct AppState {
10. db: Pool<Postgres>,
11. }
21. // configure our API to utilize the .env file
22. dotenv().ok();
23.
24. // retrieve the database url from the .env file
25. let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set!");
26.
27. // create a connection pool to the database
28. let pool = PgPoolOptions::new()
29. .max_connections(5)
30. .connect(&database_url)
31. .await
32. .expect("Error building a connection pool");
36. // share the pool with each route
37. .app_data(Data::new(AppState { db: pool.clone() }))
/src/todolist/services.rs
ファイルには、いくつかのルートが定義されています。
In our /src/todolist/services.rs
file we have a few routes defined:
- GET /todolist/entries
- POST /todolist/entries
- PUT /todolist/entries/{id}
- DELETE /todolist/entries/{id}
Before we can hook each route up to our Postgres database, we need to perform some setup.
First we need to make sure our database has the appropriate tables:
CREATE TABLE todolist_entries (
id SERIAL PRIMARY KEY,
title VARCHAR(255),
complete BOOLEAN DEFAULT FALSE
);
Next, we need to allow deserialization of query results into our TodolistEntry struct location in /src/todolist/models.rs
2. // FromRow allows deserialization of query results into Rust structs
3. use sqlx::FromRow;
And add FromRow to our TodolistEntry derive macro list:
#[derive(Serialize, Deserialize, Clone, FromRow)]
Now it's time to hook our routes up. Open up /src/todolist/services.rs.
SQLx dependency import:
4. use sqlx;
GET /todolist/entries:
6. #[get("/todolist/entries")]
7. async fn get_entries(data: web::Data<AppState>) -> impl Responder {
8. match sqlx::query_as::<_, TodolistEntry>("SELECT id, title, complete FROM todolist_entries")
9. // return all matching rows
10. .fetch_all(&data.db)
11. .await
12. {
13. Ok(entries) => HttpResponse::Ok().json(entries),
14. Err(_) => HttpResponse::InternalServerError().json("Error trying to get entries"),
15. }
16. }
POST /todolist/entries:
18. #[post("/todolist/entries")]
19. async fn create_entry(
20. data: web::Data<AppState>,
21. body: web::Json<CreateEntryBody>,
22. ) -> impl Responder {
23. let param_obj = body.into_inner();
24.
25. match sqlx::query_as::<_, TodolistEntry>("INSERT INTO todolist_entries (title) VALUES ($1) RETURNING id, title, complete")
26. // bind the param_obj.title value to the $1 argument
27. .bind(¶m_obj.title)
28. // return one row
29. .fetch_one(&data.db)
30. .await
31. {
32. Ok(entry) => HttpResponse::Ok().json(entry),
33. Err(_) => HttpResponse::InternalServerError().json("Error trying to create entry"),
34. }
35. }
PUT /todolist/entries/{id}:
39. #[put("/todolist/entries/{id}")]
40. async fn update_entry(
41. data: web::Data<AppState>,
42. path: web::Path<i32>,
43. body: web::Json<CompleteEntryBody>,
44. ) -> impl Responder {
45. let id = path.into_inner();
46. let param_obj = body.into_inner();
47.
48. match sqlx::query_as::<_, TodolistEntry>("UPDATE todolist_entries SET complete=$1 WHERE id=$2 RETURNING id, title, complete")
49. // bind the param_obj.complete value to the $1 argument
50. .bind(¶m_obj.complete)
51. // bind the id value to the $2 argument
52. .bind(&id)
53. // return the updated row
54. .fetch_one(&data.db)
55. .await
56. {
57. Ok(entry) => HttpResponse::Ok().json(entry),
58. Err(_) => HttpResponse::InternalServerError().json("Error trying to update entry"),
59. }
60. }
DELETE /todolist/entries/{id}:
62. #[delete("/todolist/entries/{id}")]
63. async fn delete_entry(data: web::Data<AppState>, path: web::Path<i32>) -> impl Responder {
64. let id = path.into_inner();
65.
66. match sqlx::query!(
67. "DELETE FROM todolist_entries WHERE id=$1",
68. // bind the id value to the $1 argument
69. id
70. )
71. // execute the query without returning any information
72. .execute(&data.db)
73. .await
74. {
75. Ok(_) => HttpResponse::Ok().json("Successfully deleted entry"),
76. Err(_) => HttpResponse::InternalServerError().json("Error trying to delete entry"),
77. }
78. }
And with that, you're ready to give your new server a test drive with your favorite HTTP request method. I personally prefer using the VS Code extension Thunder Client .
Full code can be found at https://github.com/colessar/Rust-REST-API-with-SQLx-Actix-Web
Questions about the article: click here
ありがとうございます
Discussion