Async Rust With Tokio and MongoDB

Paolo Posso
Geek Culture
Published in
4 min readNov 8, 2020

--

Photo credit: Chandler Cruttenden

I’ve been investing some time of the past two months in Rust using some online courses and YouTube videos. My first impressions: I’m really enjoying it!

In this article I intend to show in an objective way how to include some useful packages (or crates, in the Rust dialect) to your Rust project.

I’m assuming that you already have Rust installed.

To create a new project, select a folder and execute the following command on your terminal: cargo new example --bin.

In your Cargo.toml file, add the dependencies as the following:

[dependencies]
tokio = { version = "0.3", features = ["full"] }
mongodb = { version = "1.1.1", default-features = false, features = ["async-std-runtime"] }

By adding the async-std-runtime feature and default-features=false, it’s possible to use the asynchronous features for the mongo package. Otherwise you will get this really annoying error (it happened to my cousin and he found it a little hard to detect):

thread 'tests::repositories::account::should_insert_account' panicked at 'there is no timer running, must be called from the context of Tokio runtime'

After setting the dependencies, you must replace your main function, inside of the auto generated main.rs file for the following code, enabling Tokio features and making your main function async.

#[tokio::main]pub async fn main() {}

I created a connection factory to get the MongoDB connection and a repository file to place the data access code.

Structure of my infrastructure layer

In my project I also included traits for abstracting them, but this is not the goal of this article.

The code for getting the connection to the database is the following. You must include the Database and Client dependencies from mongodb crate.

MongoDB connection factory code

In this code I try to read the env variables to get MongoDB URI and DB name but if you don’t include these variables, it will take default values by using the unwrap_or() function. Note that this is an async function as well.

The repository, as mentioned before, contains the logic of the communication to the database. Note that this is also an async function and it uses await when calling the functions that return Futures, which are the values produced by the asynchronous functions (similarly to the JavaScript Promises).

The Repository struct implementation

This Account struct, used as a parameter, is a struct that I created in the domain layer of my program. Internal error is a custom error that I created by implementing the std::error::Error trait. Note that the imports on the top of the files bring these modules to be used here.

Account struct

After inserting a document on the collection, I check if there was any errors and return a custom error inside of the result. Otherwise I’m returning an empty object. The insert_one function returns the inserted id in the result. If it’s useful you can return it.

Finally you can call the repository insert function from the main.rs file or from a test file. As the function receives a self parameter, it’s a method, so you must create an “instance” of it in order to call it.

Example of call to the repository insert function

Here goes the link to the GitHub repository with the complete code. Please don’t pay attention to the mess, it’s under construction and it’s only a POC =).

That’s all, folks. See you.

--

--