Introduction: Why Rust and databases are the perfect combination
Rust is a modern systems programming language that combines the performance of C/C++ with memory safety guaranteed by the compiler. When developing backend services, web applications, and microservices, there is almost always a need to interact with databases. In this article, we will thoroughly examine how to work with two of the most popular types of databases: relational PostgreSQL and document-oriented MongoDB.
We will cover connection, performing CRUD operations, transactions, and aggregations with practical examples. All code examples are written in Rust using the tokio async runtime, which provides maximum performance.
1. Project setup and dependency installation
First, let's create a new project and add the necessary crates. Open a terminal and run:
cargo new rust_db_examplecd rust_db_exampleAdd dependencies to the Cargo.toml file:
[dependencies]tokio = { version = "1", features = ["full"] }sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] }mongodb = "2.8"serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"chrono = { version = "0.4", features = ["serde"] }Now the project structure is ready. We will use sqlx for PostgreSQL — an async driver with support for compile-time checked queries, and the official mongodb driver for working with MongoDB.
1.1. Connecting to PostgreSQL via sqlx
Let's write a module for working with PostgreSQL. Create the file src/postgres.rs:
use sqlx::postgres::PgPoolOptions;use sqlx::PgPool;use anyhow::Result;
pub async fn connect_to_postgres() -> Result<PgPool> { let database_url = "postgres://user:password@localhost:5432/mydb"; let pool = PgPoolOptions::new() .max_connections(5) .connect(database_url) .await?; println!("✅ Connected to PostgreSQL"); Ok(pool)}
// Define the user model#[derive(Debug, sqlx::FromRow)]pub struct User { pub id: i32, pub name: String, pub email: String, pub created_at: chrono::NaiveDateTime,}1.2. Connecting to MongoDB
Create the file src/mongodb.rs:
use mongodb::{Client, Collection, Database};use mongodb::bson::{doc, Document};use anyhow::Result;
pub async fn connect_to_mongodb() -> Result<(Database, Collection<Document>)> { let client = Client::with_uri_str("mongodb://localhost:27017")?; let database = client.database("mydb"); let collection = database.collection::<Document>("users"); println!("✅ Connected to MongoDB"); Ok((database, collection))}2. CRUD operations in PostgreSQL
Now let's implement the basic operations: creating, reading, updating, and deleting records.
2.1. Inserting data (CREATE)
pub async fn create_user(pool: &PgPool, name: &str, email: &str) -> Result<User> { let user = sqlx::query_as::<_, User>( "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at" ) .bind(name) .bind(email) .fetch_one(pool) .await?; println!("Created user: {:?}", user); Ok(user)}2.2. Reading data (READ)
pub async fn get_user_by_id(pool: &PgPool, user_id: i32) -> Result<Option<User>> { let user = sqlx::query_as::<_, User>( "SELECT id, name, email, created_at FROM users WHERE id = $1" ) .bind(user_id) .fetch_optional(pool) .await?; match user { Some(u) => println!("Found user: {:?}", u), None => println!("User with id={} not found"