Skip to main content
Rust Programming
CHAPTER 25 Beginner

Working with Databases in Rust

Updated: May 18, 2026
5 min read

# CHAPTER 25

Working with Databases in Rust

1. Chapter Introduction

A backend application is just a middleman between a user and a database. In the Rust ecosystem, there are two primary ways to interact with databases: ORMs (like Diesel or SeaORM) and async query builders (like SQLx). In this chapter, we will focus on SQLx, which is extremely popular because it allows you to write raw SQL while verifying your queries against the database at *compile time*.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Understand the SQLx ecosystem.
  • Connect a Rust application to an SQLite database.
  • Execute basic queries (Create tables, Insert data).
  • Fetch data and map it directly to Rust Structs.
  • Handle database connection pooling asynchronously.

3. Setting Up SQLx and SQLite

We will use SQLite for this chapter because it stores the database in a local file, requiring no server setup. *(Note: The exact same code works for PostgreSQL or MySQL by just changing the connection string!)*

1. Update Cargo.toml: We need the async runtime (Tokio) and SQLx.

toml
123
[dependencies]
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] }

4. Establishing a Connection Pool

Databases handle multiple connections. In an async server, we use a Connection Pool so multiple tasks can query the database simultaneously without waiting for a single connection to free up.
rust
1234567891011121314
use sqlx::sqlite::SqlitePoolOptions;

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    // Connect to a local SQLite file named "sqlite.db"
    // (Ensure you create this file or run `sqlite3 sqlite.db ""` in your terminal)
    let pool = SqlitePoolOptions::new()
        .max_connections(5)
        .connect("sqlite://sqlite.db").await?;

    println!("Connected to the database successfully!");
    
    Ok(())
}

5. Creating Tables and Inserting Data (Executing)

To modify the database, we use sqlx::query(). We call .execute(&pool) because we don't expect data to be returned.
rust
1234567891011121314151617181920212223242526
// Inside main...

// 1. Create a Table
sqlx::query(
    "CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        age INTEGER NOT NULL
    )"
)
.execute(&pool)
.await?;

// 2. Insert Data
let new_name = "Alice";
let new_age = 28;

// We use '?' placeholders to prevent SQL Injection attacks!
// .bind() attaches variables safely to the placeholders.
sqlx::query("INSERT INTO users (name, age) VALUES (?, ?)")
    .bind(new_name)
    .bind(new_age)
    .execute(&pool)
    .await?;

println!("User inserted!");

6. Querying Data and Mapping to Structs

The best feature of SQLx is query_as!, which automatically maps the columns returned from the SQL query into the fields of a Rust Struct!
rust
1234567891011121314151617
// Derive FromRow allows SQLx to map the database columns to this struct automatically
#[derive(sqlx::FromRow, Debug)]
struct User {
    id: i64,
    name: String,
    age: i64,
}

// Inside main...
// Fetch all users
let users = sqlx::query_as::<_, User>("SELECT id, name, age FROM users")
    .fetch_all(&pool)
    .await?;

for user in users {
    println!("Found User: ID: {}, Name: {}, Age: {}", user.id, user.name, user.age);
}

7. Compile-Time SQL Verification (The SQLx Superpower)

If you use the sqlx::query! macro instead of the sqlx::query() function, SQLx will actually connect to your database *during compilation*. If you misspell a column name in your SQL string, the Rust compiler will throw an error and refuse to build the program! This completely eliminates runtime SQL typos. *(Note: Requires installing the sqlx-cli tool and setting the DATABASE_URL environment variable).*

8. Mini Project: Student Database CLI

By combining Chapter 24 (clap) and Chapter 25 (sqlx), you can build a complete CLI app where typing cargo run -- add "Bob" 22 binds the inputs, executes an INSERT query, and saves the student to the SQLite database permanently.

9. Common Mistakes

  • String Interpolation for SQL: Never do this: sqlx::query(format!("SELECT * FROM users WHERE name = {}", name)). This allows SQL Injection attacks. Always use ? (or $1 for Postgres) and .bind().
  • Forgetting .await: SQLx is fully asynchronous. Every query returns a Future. If you forget .await, the query will never hit the database.

10. Best Practices

  • Use Postgres for Production: While SQLite is great for learning, use PostgreSQL (sqlx = { features = ["postgres"] }) for massive production web servers.
  • Connection Pools in Structs: When building a web server, store the pool inside your application state so every route can access the database safely.

11. Exercises

  1. 1. Set up a Cargo project with Tokio and SQLx.
  1. 2. Write a script that connects to an SQLite database, creates a products table (id, name, price).
  1. 3. Insert 3 products and fetch them, printing the results.

12. MCQs with Answers

Question 1

What is SQLx?

Question 2

Why is SQLx preferred over heavy ORMs by many Rust developers?

Question 3

What must you use to handle multiple database connections in an async Rust app?

Question 4

What method do you call on an SQLx query when you are inserting data and don't expect rows returned?

Question 5

How do you safely inject variables into a SQL query in SQLx to prevent SQL Injection?

Question 6

What method do you use to retrieve multiple rows of data?

Question 7

What trait must a Struct derive so SQLx can automatically map database columns to its fields?

Question 8

What happens if you forget the .await keyword at the end of an SQLx query?

Question 9

If you use the sqlx::query! macro, what incredible feature does SQLx perform?

Q10. Is SQLx restricted to only SQLite? a) Yes b) No, it natively supports PostgreSQL, MySQL, and SQLite. Answer: b) No, it supports multiple SQL databases.

13. Interview Questions

  • Q: Explain how compile-time SQL verification works in SQLx and why it is a game-changer for database development.
  • Q: What is SQL Injection, and how does .bind() prevent it?

14. Summary

Connecting to databases in Rust is remarkably safe and fast. By leveraging Tokio for async connection pooling and SQLx for type-safe query execution, you can build backend systems that handle thousands of database reads per second without the fear of runtime SQL syntax crashes.

15. Next Chapter Recommendation

We have our database ready. Now we need to expose that data to the world. In Chapter 26: Web Development with Rust, we will build a REST API using the Actix Web framework.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·