Introduction

Crates.io docs.rs License

This is the manual for the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the bson crate for BSON support. The driver contains a fully async API that supports either tokio (default) or async-std, depending on the feature flags set. The driver also has a sync API that may be enabled via feature flag.

Warning about timeouts / cancellation

In async Rust, it is common to implement cancellation and timeouts by dropping a future after a certain period of time instead of polling it to completion. This is how tokio::time::timeout works, for example. However, doing this with futures returned by the driver can leave the driver's internals in an inconsistent state, which may lead to unpredictable or incorrect behavior (see RUST-937 for more details). As such, it is highly recommended to poll all futures returned from the driver to completion. In order to still use timeout mechanisms like tokio::time::timeout with the driver, one option is to spawn tasks and time out on their JoinHandle futures instead of on the driver's futures directly. This will ensure the driver's futures will always be completely polled while also allowing the application to continue in the event of a timeout.

e.g.


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate tokio;
use std::time::Duration;
use mongodb::{
    Client,
    bson::doc,
};

async fn foo() -> std::result::Result<(), Box<dyn std::error::Error>> {

let client = Client::with_uri_str("mongodb://example.com").await?;
let collection = client.database("foo").collection("bar");
let handle = tokio::task::spawn(async move {
    collection.insert_one(doc! { "x": 1 }, None).await
});

tokio::time::timeout(Duration::from_secs(5), handle).await???;
Ok(())
}
}

Minimum supported Rust version (MSRV)

The MSRV for this crate is currently 1.53.0. This will be rarely be increased, and if it ever is, it will only happen in a minor or major version release.

Installation and Features

Importing

The driver is available on crates.io. To use the driver in your application, simply add it to your project's Cargo.toml.

[dependencies]
mongodb = "2.1.0"

Configuring the async runtime

The driver supports both of the most popular async runtime crates, namely tokio and async-std. By default, the driver will use tokio, but you can explicitly choose a runtime by specifying one of "tokio-runtime" or "async-std-runtime" feature flags in your Cargo.toml.

For example, to instruct the driver to work with async-std, add the following to your Cargo.toml:

[dependencies.mongodb]
version = "2.1.0"
default-features = false
features = ["async-std-runtime"]

Enabling the sync API

The driver also provides a blocking sync API. To enable this, add the "sync" or "tokio-sync" feature to your Cargo.toml:

[dependencies.mongodb]
version = "2.1.0"
default-features = false
features = ["sync"]

Note: The sync-specific types can be imported from mongodb::sync (e.g. mongodb::sync::Client).

All Feature Flags

FeatureDescriptionExtra dependenciesDefault
tokio-runtimeEnable support for the tokio async runtimetokio 1.0 with the full featureyes
async-std-runtimeEnable support for the async-std runtimeasync-std 1.0no
syncExpose the synchronous API (mongodb::sync), using an async-std backend. Cannot be used with the tokio-runtime feature flag.async-std 1.0no
tokio-syncExpose the synchronous API (mongodb::sync), using a tokio backend. Cannot be used with the async-std-runtime feature flag.tokio 1.0 with the full featureno
aws-authEnable support for the MONGODB-AWS authentication mechanism.reqwest 0.11no
bson-uuid-0_8Enable support for v0.8 of the uuid crate in the public API of the re-exported bson crate.n/ano
bson-chrono-0_4Enable support for v0.4 of the chrono crate in the public API of the re-exported bson crate.n/ano
bson-serde_withEnable support for the serde_with crate in the public API of the re-exported bson crate.serde_with 1.0no
zlib-compressionEnable support for compressing messages with zlibflate2 1.0no
zstd-compressionEnable support for compressing messages with zstd. This flag requires Rust version 1.54.zstd 0.9.0no
snappy-compressionEnable support for compressing messages with snappysnap 1.0.5no
openssl-tlsSwitch TLS connection handling to use 'openssl'.openssl 0.10.38no

Connecting to the Database

Connection String

Connecting to a MongoDB database requires using a connection string, a URI of the form:

mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]

At its simplest this can just specify the host and port, e.g.

mongodb://mongodb0.example.com:27017

For the full range of options supported by the Rust driver, see the documentation for the ClientOptions::parse method. That method will return a ClientOptions struct, allowing for directly querying or setting any of the options supported by the Rust driver:


#![allow(unused)]
fn main() {
extern crate mongodb;
use mongodb::options::ClientOptions;
async fn run() -> mongodb::error::Result<()> {
let mut options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?;
options.app_name = Some("My App".to_string());
Ok(())
}
}

Creating a Client

The Client struct is the main entry point for the driver. You can create one from a ClientOptions struct:


#![allow(unused)]
fn main() {
extern crate mongodb;
use mongodb::{Client, options::ClientOptions};
async fn run() -> mongodb::error::Result<()> {
let options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?;
let client = Client::with_options(options)?;
Ok(())
}
}

As a convenience, if you don't need to modify the ClientOptions before creating the Client, you can directly create one from the connection string:


#![allow(unused)]
fn main() {
extern crate mongodb;
use mongodb::Client;
async fn run() -> mongodb::error::Result<()> {
let client = Client::with_uri_str("mongodb://mongodb0.example.com:27017").await?;
Ok(())
}
}

Client uses std::sync::Arc internally, so it can safely be shared across threads or async tasks. For example:


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate tokio;
use mongodb::{bson::Document, Client, error::Result};
use tokio::task;

async fn start_workers() -> Result<()> {
let client = Client::with_uri_str("mongodb://example.com").await?;

for i in 0..5 {
    let client_ref = client.clone();

    task::spawn(async move {
        let collection = client_ref.database("items").collection::<Document>(&format!("coll{}", i));

        // Do something with the collection
    });
}

Ok(())
}
}

Client Performance

While cloning a Client is very lightweight, creating a new one is an expensive operation. For most use cases, it is highly recommended to create a single Client and persist it for the lifetime of your application. For more information, see the Performance chapter.

Reading From the Database

Database and Collection Handles

Once you have a Client, you can call Client::database to create a handle to a particular database on the server, and Database::collection to create a handle to a particular collection in that database. Database and Collection handles are lightweight - creating them requires no IO, cloneing them is cheap, and they can be safely shared across threads or async tasks. For example:


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate tokio;
use mongodb::{bson::Document, Client, error::Result};
use tokio::task;

async fn start_workers() -> Result<()> {
let client = Client::with_uri_str("mongodb://example.com").await?;
let db = client.database("items");

for i in 0..5 {
    let db_ref = db.clone();

    task::spawn(async move {
        let collection = db_ref.collection::<Document>(&format!("coll{}", i));

        // Do something with the collection
    });
}

Ok(())
}
}

A Collection can be parameterized with a type for the documents in the collection; this includes but is not limited to just Document. The various methods that accept instances of the documents (e.g. Collection::insert_one) require that it implement the Serialize trait from the serde crate. Similarly, the methods that return instances (e.g. Collection::find_one) require that it implement Deserialize.

Document implements both and can always be used as the type parameter. However, it is recommended to define types that model your data which you can parameterize your Collections with instead, since doing so eliminates a lot of boilerplate deserialization code and is often more performant.


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate tokio;
extern crate serde;
use mongodb::{
    bson::doc,
    error::Result,
};
use tokio::task;

async fn start_workers() -> Result<()> {
use mongodb::Client;

let client = Client::with_uri_str("mongodb://example.com").await?;
use serde::{Deserialize, Serialize};

// Define a type that models our data.
#[derive(Clone, Debug, Deserialize, Serialize)]
struct Item {
    id: u32,
}

// Parameterize our collection with the model.
let coll = client.database("items").collection::<Item>("in_stock");

for i in 0..5 {
    // Perform operations that work with directly our model.
    coll.insert_one(Item { id: i }, None).await;
}

Ok(())
}
}

For more information, see the Serde Integration section.

Cursors

Results from queries are generally returned via Cursor, a struct which streams the results back from the server as requested. The Cursor type implements the Stream trait from the futures crate, and in order to access its streaming functionality you need to import at least one of the StreamExt or TryStreamExt traits.

# In Cargo.toml, add the following dependency.
futures = "0.3"

#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate serde;
extern crate futures;
use serde::Deserialize;
#[derive(Deserialize)]
struct Book { title: String }
async fn foo() -> mongodb::error::Result<()> {
let typed_collection = mongodb::Client::with_uri_str("").await?.database("").collection::<Book>("");
// This trait is required to use `try_next()` on the cursor
use futures::stream::TryStreamExt;
use mongodb::{bson::doc, options::FindOptions};

// Query the books in the collection with a filter and an option.
let filter = doc! { "author": "George Orwell" };
let find_options = FindOptions::builder().sort(doc! { "title": 1 }).build();
let mut cursor = typed_collection.find(filter, find_options).await?;

// Iterate over the results of the cursor.
while let Some(book) = cursor.try_next().await? {
    println!("title: {}", book.title);
}
Ok(()) }
}

Performance

Client Best Practices

The Client handles many aspects of database connection behind the scenes that can require manual management for other database drivers; it discovers server topology, monitors it for any changes, and maintains an internal connection pool. This has implications for how a Client should be used for best performance.

Lifetime

A Client should be as long-lived as possible. Establishing a new Client is relatively slow and resource-intensive, so ideally that should only be done once at application startup. Because Client is implemented using an internal Arc, it can safely be shared across threads or tasks, and cloneing it to pass to new contexts is extremely cheap.


#![allow(unused)]
fn main() {
extern crate mongodb;
use mongodb::Client;
use std::error::Error;
// This will be very slow because it's constructing and tearing down a `Client`
// with every request.
async fn handle_request_bad() -> Result<(), Box<dyn Error>> {
    let client = Client::with_uri_str("mongodb://example.com").await?;
    // Do something with the client
    Ok(())
}

// This will be much faster.
async fn handle_request_good(client: &Client) -> Result<(), Box<dyn Error>> {
    // Do something with the client
    Ok(())
}
}

This is especially noticeable when using a framework that provides connection pooling; because Client does its own pooling internally, attempting to maintain a pool of Clients will (somewhat counter-intuitively) result in worse performance than using a single one.

Runtime

A Client is implicitly bound to the instance of the tokio or async-std runtime in which it was created. Attempting to execute operations on a different runtime instance will cause incorrect behavior and unpredictable failures. This is easy to accidentally invoke when testing, as the tokio::test or async_std::test helper macros create a new runtime for each test.


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate once_cell;
extern crate tokio;
use mongodb::Client;
use std::error::Error;
use tokio::runtime::Runtime;
use once_cell::sync::Lazy;

static CLIENT: Lazy<Client> = Lazy::new(|| {
    let rt = Runtime::new().unwrap();
    rt.block_on(async {
        Client::with_uri_str("mongodb://example.com").await.unwrap()
    })
});

// This will inconsistently fail.
#[tokio::test]
async fn test_list_dbs() -> Result<(), Box<dyn Error>> {
    CLIENT.list_database_names(None, None).await?;
    Ok(())
}
}

To work around this issue, either create a new Client for every async test, or bundle the Runtime along with the client and don't use the test helper macros.


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate once_cell;
extern crate tokio;
use mongodb::Client;
use std::error::Error;
use tokio::runtime::Runtime;
use once_cell::sync::Lazy;

static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| {
    let rt = Runtime::new().unwrap();
    let client = rt.block_on(async {
        Client::with_uri_str("mongodb://example.com").await.unwrap()
    });
    (client, rt)
});

#[test]
fn test_list_dbs() -> Result<(), Box<dyn Error>> {
    let (client, rt) = &*CLIENT_RUNTIME;
    rt.block_on(async {
        client.list_database_names(None, None).await
    })?;
    Ok(())
}
}

or


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate tokio;
use mongodb::Client;
use std::error::Error;
#[tokio::test]
async fn test_list_dbs() -> Result<(), Box<dyn Error>> {
    let client = Client::with_uri_str("mongodb://example.com").await?;
    CLIENT.list_database_names(None, None).await?;
    Ok(())
}
}

Parallelism

Where data operations are naturally parallelizable, spawning many asynchronous tasks that use the driver concurrently is often the best way to achieve maximum performance, as the driver is designed to work well in such situations.


#![allow(unused)]
fn main() {
extern crate mongodb;
extern crate tokio;
use mongodb::{bson::Document, Client, error::Result};
use tokio::task;

async fn start_workers() -> Result<()> {
let client = Client::with_uri_str("mongodb://example.com").await?;

for i in 0..5 {
    let client_ref = client.clone();

    task::spawn(async move {
        let collection = client_ref.database("items").collection::<Document>(&format!("coll{}", i));

        // Do something with the collection
    });
}

Ok(())
}
}