---
title: "Diesel vs SQLx: Qual ORM Rust Usar em 2026? | Rust Brasil"
url: "https://rustlang.com.br/artigos/diesel-vs-sqlx/"
markdown_url: "https://rustlang.com.br/artigos/diesel-vs-sqlx.MD"
description: "Diesel vs SQLx: ORM vs query builder em Rust. Verificação em compile-time, async, migrations e type safety. Qual escolher?"
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Diesel vs SQLx: Qual ORM Rust Usar em 2026? | Rust Brasil

Diesel vs SQLx: ORM vs query builder em Rust. Verificação em compile-time, async, migrations e type safety. Qual escolher?


## Introdução

Ao trabalhar com bancos de dados em Rust, duas bibliotecas se destacam: **Diesel** e **SQLx**. Embora ambas resolvam o mesmo problema — comunicar sua aplicação com o banco de dados — elas adotam abordagens fundamentalmente diferentes.

**[Diesel](/ecossistema/diesel/)** é um ORM (Object-Relational Mapper) completo com um DSL de queries fortemente tipado. Ele gera SQL a partir de código Rust e verifica tudo em tempo de compilação usando seu schema DSL. É síncrono por padrão, embora suporte async via `diesel-async`.

**[SQLx](/ecossistema/sqlx/)** é um query builder que executa SQL puro, mas com uma característica única: ele verifica suas queries SQL contra um banco de dados real em tempo de compilação usando a macro `query!`. É async-first e suporta múltiplos runtimes.

Ambas garantem type safety em tempo de compilação, mas de maneiras completamente distintas. Vamos explorar essas diferenças em detalhes.

## Tabela Comparativa

| Característica | Diesel | SQLx |
|---|---|---|
| **Tipo** | ORM completo | Query builder / SQL puro |
| **Async** | Via `diesel-async` | Nativo |
| **Verificação compile-time** | Via schema DSL | Via macro `query!` (requer DB) |
| **Migrations** | CLI `diesel migration` | CLI `sqlx migrate` |
| **Bancos suportados** | PostgreSQL, MySQL, SQLite | PostgreSQL, MySQL, SQLite |
| **DSL de queries** | Sim (Rust puro) | Não (SQL puro) |
| **Pool de conexões** | Via `r2d2` ou `deadpool` | Integrado (`PgPool`) |
| **Curva de aprendizado** | Alta (DSL próprio) | Moderada (SQL direto) |
| **Downloads mensais** | ~7M+ | ~12M+ |

## Dependências no Cargo.toml

Para Diesel (com PostgreSQL):

```toml
[dependencies]
diesel = { version = "2", features = ["postgres"] }
dotenvy = "0.15"

# Para async (opcional)
diesel-async = { version = "0.5", features = ["postgres", "deadpool"] }
tokio = { version = "1", features = ["full"] }
```

Para SQLx (com PostgreSQL):

```toml
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "uuid", "migrate"] }
tokio = { version = "1", features = ["full"] }
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4"] }
```

## Setup e Migrations

### Diesel

O Diesel usa uma CLI dedicada para gerenciar o schema:

```bash
# Instalar a CLI
cargo install diesel_cli --no-default-features --features postgres

# Configurar
echo "DATABASE_URL=postgres://user:pass@localhost/meu_banco" > .env
diesel setup

# Criar migration
diesel migration generate criar_tarefas
```

O arquivo de migration (SQL):

```sql
-- up.sql
CREATE TABLE tarefas (
    id SERIAL PRIMARY KEY,
    titulo VARCHAR NOT NULL,
    descricao TEXT,
    concluida BOOLEAN NOT NULL DEFAULT FALSE,
    criada_em TIMESTAMP NOT NULL DEFAULT NOW()
);

-- down.sql
DROP TABLE tarefas;
```

Após rodar `diesel migration run`, o Diesel gera automaticamente um arquivo `src/schema.rs`:

```rust
// src/schema.rs (gerado automaticamente)
diesel::table! {
    tarefas (id) {
        id -> Int4,
        titulo -> Varchar,
        descricao -> Nullable<Text>,
        concluida -> Bool,
        criada_em -> Timestamp,
    }
}
```

### SQLx

O SQLx também tem uma CLI:

```bash
# Instalar a CLI
cargo install sqlx-cli --no-default-features --features postgres

# Criar banco
sqlx database create

# Criar migration
sqlx migrate add criar_tarefas
```

A migration é SQL puro:

```sql
-- migrations/20260223000000_criar_tarefas.sql
CREATE TABLE tarefas (
    id SERIAL PRIMARY KEY,
    titulo VARCHAR NOT NULL,
    descricao TEXT,
    concluida BOOLEAN NOT NULL DEFAULT FALSE,
    criada_em TIMESTAMP NOT NULL DEFAULT NOW()
);
```

```rust
// Executar migrations no código
sqlx::migrate!("./migrations")
    .run(&pool)
    .await?;
```

## Comparação de Código: CRUD Completo

### Models

**Diesel:**

```rust
use diesel::prelude::*;
use crate::schema::tarefas;

#[derive(Queryable, Selectable, Debug)]
#[diesel(table_name = tarefas)]
pub struct Tarefa {
    pub id: i32,
    pub titulo: String,
    pub descricao: Option<String>,
    pub concluida: bool,
    pub criada_em: chrono::NaiveDateTime,
}

#[derive(Insertable)]
#[diesel(table_name = tarefas)]
pub struct NovaTarefa<'a> {
    pub titulo: &'a str,
    pub descricao: Option<&'a str>,
}
```

**SQLx:**

```rust
use sqlx::FromRow;
use chrono::NaiveDateTime;

#[derive(Debug, FromRow)]
pub struct Tarefa {
    pub id: i32,
    pub titulo: String,
    pub descricao: Option<String>,
    pub concluida: bool,
    pub criada_em: NaiveDateTime,
}
```

No SQLx, não há necessidade de um struct separado para inserção — os valores são passados diretamente como parâmetros da query.

### INSERT

**Diesel:**

```rust
use diesel::prelude::*;
use crate::schema::tarefas;
use crate::models::{Tarefa, NovaTarefa};

fn criar_tarefa(conn: &mut PgConnection, titulo: &str, desc: Option<&str>) -> Tarefa {
    let nova = NovaTarefa {
        titulo,
        descricao: desc,
    };

    diesel::insert_into(tarefas::table)
        .values(&nova)
        .returning(Tarefa::as_returning())
        .get_result(conn)
        .expect("Erro ao inserir tarefa")
}
```

**SQLx:**

```rust
use sqlx::PgPool;
use crate::models::Tarefa;

async fn criar_tarefa(pool: &PgPool, titulo: &str, desc: Option<&str>) -> Result<Tarefa, sqlx::Error> {
    sqlx::query_as::<_, Tarefa>(
        "INSERT INTO tarefas (titulo, descricao) VALUES ($1, $2) RETURNING *"
    )
    .bind(titulo)
    .bind(desc)
    .fetch_one(pool)
    .await
}
```

Ou com a macro verificada em tempo de compilação:

```rust
async fn criar_tarefa(pool: &PgPool, titulo: &str, desc: Option<&str>) -> Result<Tarefa, sqlx::Error> {
    sqlx::query_as!(
        Tarefa,
        "INSERT INTO tarefas (titulo, descricao) VALUES ($1, $2) RETURNING *",
        titulo,
        desc
    )
    .fetch_one(pool)
    .await
}
```

### SELECT

**Diesel:**

```rust
use diesel::prelude::*;
use crate::schema::tarefas::dsl::*;
use crate::models::Tarefa;

fn listar_tarefas(conn: &mut PgConnection) -> Vec<Tarefa> {
    tarefas
        .filter(concluida.eq(false))
        .order(criada_em.desc())
        .limit(10)
        .select(Tarefa::as_select())
        .load(conn)
        .expect("Erro ao buscar tarefas")
}

fn buscar_por_id(conn: &mut PgConnection, tarefa_id: i32) -> Option<Tarefa> {
    tarefas
        .find(tarefa_id)
        .select(Tarefa::as_select())
        .first(conn)
        .optional()
        .expect("Erro ao buscar tarefa")
}
```

**SQLx:**

```rust
use sqlx::PgPool;
use crate::models::Tarefa;

async fn listar_tarefas(pool: &PgPool) -> Result<Vec<Tarefa>, sqlx::Error> {
    sqlx::query_as!(
        Tarefa,
        "SELECT * FROM tarefas WHERE concluida = false ORDER BY criada_em DESC LIMIT 10"
    )
    .fetch_all(pool)
    .await
}

async fn buscar_por_id(pool: &PgPool, tarefa_id: i32) -> Result<Option<Tarefa>, sqlx::Error> {
    sqlx::query_as!(
        Tarefa,
        "SELECT * FROM tarefas WHERE id = $1",
        tarefa_id
    )
    .fetch_optional(pool)
    .await
}
```

### UPDATE

**Diesel:**

```rust
use diesel::prelude::*;
use crate::schema::tarefas::dsl::*;
use crate::models::Tarefa;

fn concluir_tarefa(conn: &mut PgConnection, tarefa_id: i32) -> Option<Tarefa> {
    diesel::update(tarefas.find(tarefa_id))
        .set(concluida.eq(true))
        .returning(Tarefa::as_returning())
        .get_result(conn)
        .optional()
        .expect("Erro ao atualizar tarefa")
}
```

**SQLx:**

```rust
async fn concluir_tarefa(pool: &PgPool, tarefa_id: i32) -> Result<Option<Tarefa>, sqlx::Error> {
    sqlx::query_as!(
        Tarefa,
        "UPDATE tarefas SET concluida = true WHERE id = $1 RETURNING *",
        tarefa_id
    )
    .fetch_optional(pool)
    .await
}
```

### DELETE

**Diesel:**

```rust
use diesel::prelude::*;
use crate::schema::tarefas::dsl::*;

fn deletar_tarefa(conn: &mut PgConnection, tarefa_id: i32) -> usize {
    diesel::delete(tarefas.find(tarefa_id))
        .execute(conn)
        .expect("Erro ao deletar tarefa")
}
```

**SQLx:**

```rust
async fn deletar_tarefa(pool: &PgPool, tarefa_id: i32) -> Result<u64, sqlx::Error> {
    let result = sqlx::query!("DELETE FROM tarefas WHERE id = $1", tarefa_id)
        .execute(pool)
        .await?;
    Ok(result.rows_affected())
}
```

## Conexão e Pool

**Diesel (síncrono com r2d2):**

```rust
use diesel::pg::PgConnection;
use diesel::r2d2::{self, ConnectionManager};

type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;

fn criar_pool(database_url: &str) -> DbPool {
    let manager = ConnectionManager::<PgConnection>::new(database_url);
    r2d2::Pool::builder()
        .max_size(15)
        .build(manager)
        .expect("Falha ao criar pool")
}
```

**SQLx (async nativo):**

```rust
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;

async fn criar_pool(database_url: &str) -> PgPool {
    PgPoolOptions::new()
        .max_connections(15)
        .connect(database_url)
        .await
        .expect("Falha ao criar pool")
}
```

## Performance e Compilação

### Tempo de compilação

O Diesel gera toda a verificação através do schema DSL em Rust — não precisa de conexão com o banco durante a compilação. Já o SQLx com a macro `query!` precisa de acesso ao banco em tempo de compilação (ou de um cache gerado com `cargo sqlx prepare`).

Para CI/CD, o SQLx oferece o modo offline:

```bash
# Gerar cache de metadados
cargo sqlx prepare

# No CI, compilar sem banco de dados
SQLX_OFFLINE=true cargo build
```

### Performance em runtime

Ambas as bibliotecas geram queries SQL eficientes. O Diesel pode ter uma leve vantagem em cenários com queries simples, pois o SQL é gerado estaticamente. O SQLx envia SQL literal, o que pode ser mais eficiente para queries complexas que você otimizou manualmente.

Na prática, o gargalo de performance é quase sempre o banco de dados, não a biblioteca Rust.

## Quando Escolher Cada Um

### Escolha Diesel quando:

- Prefere um **DSL Rust-nativo** ao invés de escrever SQL
- Quer verificação em **tempo de compilação sem banco de dados** conectado
- Precisa de **suporte maduro** com anos de uso em produção
- Trabalha com uma equipe que **não domina SQL** avançado
- Prefere **patterns de ORM** tradicionais (ActiveRecord-like)

### Escolha SQLx quando:

- Prefere escrever **SQL puro** e ter controle total sobre as queries
- Precisa de **suporte async nativo** integrado com Tokio
- Quer **verificação de SQL** contra o banco real em tempo de compilação
- Trabalha com **queries complexas** (JOINs, CTEs, window functions)
- Está usando um framework async como [Axum](/artigos/axum-vs-actix/)
- Precisa de **features específicas** do banco (jsonb, arrays, etc.)

### Recomendação prática

Para projetos novos em 2026 com Axum ou outro framework async, **SQLx tende a ser a melhor escolha** pela integração nativa com async e pela flexibilidade do SQL puro. Para projetos que valorizam abstração sobre o banco e patterns de ORM, o **Diesel continua sendo excelente**.

Também vale considerar o **SeaORM**, que combina o melhor dos dois mundos: uma DSL de queries com suporte async nativo sobre SQLx.

## Guia de Migração: Diesel para SQLx

Se você está migrando do Diesel para o SQLx:

1. **Migrations:** As migrations são SQL puro em ambos — copie os arquivos `.sql` diretamente
2. **Models:** Troque `#[derive(Queryable)]` por `#[derive(FromRow)]`
3. **Queries:** Converta as queries do DSL Diesel para SQL puro nas macros `query!`/`query_as!`
4. **Pool:** Substitua `r2d2::Pool` por `PgPool` do SQLx
5. **Schema:** Remova o `schema.rs` — não é mais necessário
6. **Prepare offline:** Execute `cargo sqlx prepare` para CI

## Conclusão

Diesel e SQLx representam duas filosofias válidas para acesso a bancos de dados em Rust. O Diesel oferece a segurança de um DSL fortemente tipado, enquanto o SQLx combina SQL puro com verificação em tempo de compilação. Ambos são excelentes ferramentas — a escolha depende do estilo da sua equipe e das necessidades do projeto.

## Veja Também

- [Tutorial: Rust com PostgreSQL](/tutoriais/rust-postgresql/)
- [Receita: Conectar ao PostgreSQL](/receitas/conectar-postgresql/)
- [Axum vs Actix Web: Qual Framework Escolher](/artigos/axum-vs-actix/)
- [Tokio: O Runtime Async Mais Popular de Rust](/artigos/tokio-guia-completo/)
- [Serde: O Guia Completo de Serialização](/artigos/serde-guia-completo/)
- [Error Handling: anyhow vs thiserror vs miette](/artigos/error-handling-libs/)

---

Veja como outras linguagens lidam com acesso a bancos de dados e ORMs:

- <a href="https://kotlin.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'kotlin.dev.br' })">Exposed e Hibernate em Kotlin: ORMs tipados para a JVM</a>
- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">GORM e sqlx em Go: acesso a bancos de dados com simplicidade</a>
