diff --git a/Cargo.toml b/Cargo.toml index 429362ad..d2fb892b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "basics/todo", "database_interactions/basic", "database_interactions/diesel", + "database_interactions/mongodb", "database_interactions/pg", "database_interactions/r2d2", "database_interactions/redis", diff --git a/database_interactions/mongodb/Cargo.toml b/database_interactions/mongodb/Cargo.toml new file mode 100644 index 00000000..bc6ca326 --- /dev/null +++ b/database_interactions/mongodb/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mongodb" +version = "0.1.0" +authors = ["Isabel Atkinson "] +edition = "2018" + +[dependencies] +actix-rt = "2.2.0" +actix-web = "4.0.0-beta.8" +futures-util = "0.3.17" +mongodb = "2.0.0" +serde = { version = "1.0", features = ["derive"] } diff --git a/database_interactions/mongodb/README.md b/database_interactions/mongodb/README.md new file mode 100644 index 00000000..cad53462 --- /dev/null +++ b/database_interactions/mongodb/README.md @@ -0,0 +1,25 @@ +# MongoDB + +Simple example of MongoDB usage with Actix web. For more information on the MongoDB Rust driver, +visit the [documentation](https://docs.rs/mongodb/2.0.0/mongodb/index.html) and +[source code](https://github.com/mongodb/mongo-rust-driver). + +## Usage + +### Install MongoDB + +Visit the [MongoDB Download Center](https://www.mongodb.com/try) for instructions on how to use +MongoDB Atlas or set up MongoDB locally. + +### Set an environment variable + +The example code creates a client with the URI set by the `MONGODB_URI` environment variable. The +default URI for a standalone `mongod` running on localhost is "mongodb://localhost:27017". For more +information on MongoDB URIs, visit the +[connection string](https://docs.mongodb.com/manual/reference/connection-string/) entry in the +MongoDB manual. + +### Run the example + +To execute the example code, run `cargo run` in the `database_interactions/mongodb` directory. + diff --git a/database_interactions/mongodb/src/main.rs b/database_interactions/mongodb/src/main.rs new file mode 100644 index 00000000..66c9e0be --- /dev/null +++ b/database_interactions/mongodb/src/main.rs @@ -0,0 +1,77 @@ +//! Example code for using MongoDB with Actix. + +mod model; +#[cfg(test)] +mod test; + +use actix_web::{get, post, web, App, HttpResponse, HttpServer}; +use mongodb::{bson::doc, options::IndexOptions, Client, Collection, IndexModel}; + +use model::User; + +const DB_NAME: &str = "myApp"; +const COLL_NAME: &str = "users"; + +/// Adds a new user to the "users" collection in the database. +#[post("/add_user")] +async fn add_user(client: web::Data, form: web::Form) -> HttpResponse { + let collection = client.database(DB_NAME).collection(COLL_NAME); + let result = collection.insert_one(form.into_inner(), None).await; + match result { + Ok(_) => HttpResponse::Ok().body("user added"), + Err(err) => HttpResponse::InternalServerError().body(err.to_string()), + } +} + +/// Gets the user with the supplied username. +#[get("/get_user/{username}")] +async fn get_user( + client: web::Data, + username: web::Path, +) -> HttpResponse { + let username = username.into_inner(); + let collection: Collection = client.database(DB_NAME).collection(COLL_NAME); + match collection + .find_one(doc! { "username": &username }, None) + .await + { + Ok(Some(user)) => HttpResponse::Ok().json(user), + Ok(None) => HttpResponse::NotFound() + .body(format!("No user found with username {}", username)), + Err(err) => HttpResponse::InternalServerError().body(err.to_string()), + } +} + +/// Creates an index on the "username" field to force the values to be unique. +async fn create_username_index(client: &Client) { + let options = IndexOptions::builder().unique(true).build(); + let model = IndexModel::builder() + .keys(doc! { "username": 1 }) + .options(options) + .build(); + client + .database(DB_NAME) + .collection::(COLL_NAME) + .create_index(model, None) + .await + .expect("creating an index should succeed"); +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let uri = std::env::var("MONGODB_URI") + .unwrap_or_else(|_| "mongodb://localhost:27017".into()); + + let client = Client::with_uri_str(uri).await.expect("failed to connect"); + create_username_index(&client).await; + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(client.clone())) + .service(add_user) + .service(get_user) + }) + .bind("127.0.0.1:8080")? + .run() + .await +} diff --git a/database_interactions/mongodb/src/model.rs b/database_interactions/mongodb/src/model.rs new file mode 100644 index 00000000..1e39dc34 --- /dev/null +++ b/database_interactions/mongodb/src/model.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct User { + pub first_name: String, + pub last_name: String, + pub username: String, + pub email: String, +} diff --git a/database_interactions/mongodb/src/test.rs b/database_interactions/mongodb/src/test.rs new file mode 100644 index 00000000..bf621511 --- /dev/null +++ b/database_interactions/mongodb/src/test.rs @@ -0,0 +1,54 @@ +use actix_web::{ + test::{init_service, read_response, read_response_json, TestRequest}, + web::Bytes, +}; +use mongodb::Client; + +use super::*; + +#[actix_rt::test] +#[ignore = "requires MongoDB instance running"] +async fn test() { + let uri = std::env::var("MONGODB_URI") + .unwrap_or_else(|_| "mongodb://localhost:27017".into()); + + let client = Client::with_uri_str(uri).await.expect("failed to connect"); + + // Clear any data currently in the users collection. + client + .database(DB_NAME) + .collection::(COLL_NAME) + .drop(None) + .await + .expect("drop collection should succeed"); + + let mut app = init_service( + App::new() + .app_data(web::Data::new(client)) + .service(add_user) + .service(get_user), + ) + .await; + + let user = User { + first_name: "Jane".into(), + last_name: "Doe".into(), + username: "janedoe".into(), + email: "example@example.com".into(), + }; + + let req = TestRequest::post() + .uri("/add_user") + .set_form(&user) + .to_request(); + + let response = read_response(&mut app, req).await; + assert_eq!(response, Bytes::from_static(b"user added")); + + let req = TestRequest::get() + .uri(&format!("/get_user/{}", &user.username)) + .to_request(); + + let response: User = read_response_json(&mut app, req).await; + assert_eq!(response, user); +}