Introduction to Working with Databases in Rust
Rust is rapidly gaining popularity in systems programming, but its capabilities extend far beyond low-level tasks. Thanks to its strict type system, memory safety, and high performance, Rust is an excellent choice for building reliable server applications that work with databases. In this article, we will thoroughly examine how to connect and perform operations with two popular DBMS: the relational PostgreSQL and the document-oriented MongoDB.
We will look at the main libraries (crates), such as diesel for PostgreSQL and the official mongodb driver, and also show examples of CRUD operations in pure Rust. The article will be useful for both beginners and experienced developers looking to expand their technology stack.
1. Working with PostgreSQL: the Diesel Crate
Diesel is a safe, extensible ORM and query builder for Rust. It provides compile-time type checking, which eliminates many runtime errors associated with SQL queries. To get started, you need to add dependencies to the Cargo.toml file.
Installation and Setup
Add the following crates:
[dependencies]diesel = { version = "2.1", features = ["postgres"] }dotenvy = "0.15"serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"tokio = { version = "1.0", features = ["full"] }Also, install the Diesel CLI command-line utility:
cargo install diesel_cli --no-default-features --features postgresCreate a .env file in the project root with your database URL:
DATABASE_URL=postgres://username:password@localhost/my_databaseRun the command to set up migrations:
diesel setupdiesel migration generate create_usersDefining the Schema and Model
In the migration file up.sql, write the SQL to create the table:
CREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR NOT NULL, email VARCHAR NOT NULL UNIQUE, age INTEGER NOT NULL);Then run diesel migration run. After that, generate the Rust schema (file src/schema.rs) using:
diesel print-schema > src/schema.rsNow let's define the model and structure for inserting data:
// src/models.rsuse diesel::prelude::*;use serde::{Deserialize, Serialize};use crate::schema::users;
#[derive(Queryable, Selectable, Serialize, Deserialize)]#[diesel(table_name = users)]pub struct User { pub id: i32, pub name: String, pub email: String, pub age: i32,}
#[derive(Insertable, Deserialize)]#[diesel(table_name = users)]pub struct NewUser { pub name: String, pub email: String, pub age: i32,}CRUD Operations with PostgreSQL
Let's look at the basic operations. Connecting to the database and creating a new user:
use diesel::prelude::*;use dotenvy::dotenv;use std::env;
use crate::models::{NewUser, User};use crate::schema::users::dsl::*;
pub fn establish_connection() -> PgConnection { dotenv().ok(); let database_url = env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); PgConnection::establish(&database_url) .expect(&format!("Error connecting to {}", database_url))}
pub fn create_user(conn: &mut PgConnection, name: &str, email: &str, age: i32) -> User { let new_user = NewUser { name: name.to_string(), email: email.to_string(), age, };
diesel::insert_into(users) .values(&new_user) .returning(User::as_returning()) .get_result(conn)```