mongodbrustasync-awaitrust-rocket

How to use MongoDB driver in Rocket.rs?


I have simple rocket.rs app with register route
Here it's service with mongodb sync driver

pub async fn create_new_user(user_email: &str, password: &str, user_name: &str) -> Result<(), &'static str> {

  let users_collection: mongodb::Collection<UserModel> = DB.collection("users");

  // check if user with given email or name already exists
  let existing_user = users_collection.find_one(
    doc! {
      "$or": [
        {"user_email": user_email},
        {"user_name": user_name}
      ]
    },
    None
  );

  if existing_user.is_ok() {
    return Err("User with such email or name already exists");
  }


  // new user struct
  let new_user = UserModel {
    user_id: /* hash gen fn */,
    user_name: user_name.to_string(),
    email: user_email.to_string(),
    hashed_password: password.to_string(),
    verified: false,
  };



  // insert new user into db
  let insert_result = DB.collection("users").insert_one(new_user, None);

  if insert_result.is_ok() {
    return Ok(());
  } else {
    return Err("Failed to create user");
  }

}

Unfortunately, rocket refuses to work with sync driver and i need an async one

How to add it into app? Cuz i have no idea and only stuff i found in google is overcomplicated code i don't understand or async driver docs...

// how it was with sync one...
static DB: Lazy<Database> = Lazy::new(
    || {
        let client = Client::with_uri_str(&CONFIG.mongodb_uri_string);
        client.database("silly-tips")
    }
);
// now
static DB: once_cell:unsync::Lazy<Database> = once_cell:unsync::Lazy::new(
    async {
        let client = Client::with_uri_str(&CONFIG.mongodb_uri_string).await.expect("Error connecting to MongoDB");
        client.database("silly-tips")
    }
);
`Cell<std::option::Option<fn() -> Database>>` cannot be shared between threads safely
within `once_cell::unsync::Lazy<Database>`, the trait `Sync` is not implemented for `Cell<std::option::Option<fn() -> Database>>`, which is required by `once_cell::unsync::Lazy<Database>: Sync`
if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock`
shared static variables must have a type that implements `Sync`rustcClick for full compiler diagnostic


Solution

  • There are a two ways to go about it:

    Keep using a global variable

    If you want to mimic your existing setup using static DB: Lazy<Database>, you can keep doing that using tokio::sync::OnceCell for instance:

    use mongodb::{Client, Database};
    use tokio::sync::OnceCell;
    
    async fn get_db() -> &'static Database {
        static DB: OnceCell<Database> = OnceCell::const_new();
    
        DB.get_or_init(|| async {
            Client::with_uri_str(&CONFIG.mongodb_uri_string)
                .await
                .expect("Error connecting to MongoDB")
                .database("silly-tips")
        })
        .await
    }
    
    pub async fn create_new_user(...) {
        let users_collection = get_db().await.collection::<UserModel>("users");
        ...
    }
    

    See a few other ways here: How to use lazy_static with async/await initializer?

    Use Rocket's state

    The other option is to have Rocket manage the state for you. You do this by giving the database to Rocket via .manage() from which you can receive in your Rocket routes via the State request guard. Then you would just pass it into your create_new_user function as a normal parameter:

    use mongodb::{Client, Database};
    use rocket::{launch, post, routes, State};
    
    pub async fn create_new_user(db: &Database, ...) {
        let users_collection = db.collection::<UserModel>("users");
        ...
    }
    
    #[post("/login")]
    async fn login(db: &State<Database>) {
        ...
        create_new_user(&db, ...).await;
        ...
    }
    
    #[launch]
    async fn rocket() -> _ {
        ...
    
        let db = Client::with_uri_str(&CONFIG.mongodb_uri_string)
            .await
            .expect("Error connecting to MongoDB")
            .database("silly-tips");
    
        rocket::build().manage(db).mount("/", routes![login])
    }
    

    This is generally preferred since you avoid global variables.