Carregando agora

Rust API com Actix: Como construir seu primeiro CRUD completo

Desenvolvedor criando uma API com Rust e Actix Web, com código na tela, logotipo do Rust e PostgreSQL em destaque.

Rust API com Actix é uma combinação perfeita para quem quer performance, segurança e, claro, aquele gostinho de modernidade no desenvolvimento backend. Neste artigo, vou mostrar como montar um CRUD completo usando o Actix Web, sem enrolação e com muita prática. Ah, e se você ainda acha que Rust é só pra sistemas de baixo nível, se prepara pra mudar de ideia.


Por que escolher Actix Web?

Se você já programou backend com Node.js ou Python e ficou meio traumatizado com consumo de memória ou problemas de concorrência, Rust pode ser sua luz no fim do túnel. E com Actix Web, criar APIs é quase como usar magia negra (no bom sentido).

Actix Web é um framework rápido, seguro e fácil de usar. Quer dizer, fácil pra quem já passou pelo básico do Rust. Se não, recomendo assistir a uns tutoriais mais “mamão com açúcar” antes de meter a cara aqui.


Preparando o ambiente para seu CRUD

Antes de tudo, você vai precisar de:

  1. Rust instalado na sua máquina. Se ainda não tem, é só rodar: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  2. Cargo, que já vem junto com o Rust.
  3. Um editor de texto decente. Recomendo VS Code com extensão pra Rust.
  4. PostgreSQL ou outro banco de dados que você prefira usar.

Com isso na mão, bora criar o projeto:

cargo new minha_api
cd minha_api

Agora que temos o projeto criado, é hora de adicionar as dependências no Cargo.toml:

[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.6", features = ["runtime-actix-native-tls", "postgres"] }
dotenv = "0.15"
bcrypt = "0.10"

Instalou tudo? Então bora pro código.


Configurando o projeto

Preparando o arquivo .env

Antes de conectar com o banco, crie um arquivo .env na raiz do projeto com as variáveis de ambiente:

DATABASE_URL=postgres://user:password@localhost/minha_api_db

Troque user, password e minha_api_db pelos dados do seu PostgreSQL. Com isso feito, carregue o .env no projeto. Altere o arquivo main.rs:

use actix_web::{App, HttpServer};
use dotenv::dotenv;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv().ok();
    HttpServer::new(|| {
        App::new()
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Rodou sem erro? Parabéns, o Actix Web já tá rodando na porta 8080.


Montando o CRUD

Estruturando o projeto

Vamos organizar as coisas. Crie as seguintes pastas e arquivos:

mkdir src/database src/services

touch src/database/mod.rs src/database/connection.rs

touch src/services/mod.rs src/services/user.rs

Configuração de conexão

No arquivo src/database/connection.rs, configure a conexão com o PostgreSQL:

use sqlx::{PgPool, postgres::PgPoolOptions};
use std::env;

pub async fn connect() -> PgPool {
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL não configurada no .env");

    PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await
        .expect("Falha ao conectar ao banco de dados")
}

Agora importe isso no src/database/mod.rs:

pub mod connection;

Criando o banco e as migrations

Para não fazer tudo manualmente, use o SQLx para rodar migrations. Primeiro, instale a CLI do SQLx:

cargo install sqlx-cli --no-default-features --features postgres

Depois, crie a primeira migration:

sqlx migrate add create_users_table

No arquivo criado em migrations, adicione:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    password TEXT NOT NULL
);

Rode as migrations:

sqlx migrate run

Para saber mais sobre o uso de SQLx, confira a documentação oficial.

Criando os modelos

No src/services/user.rs, adicione:

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct User {
    pub id: i32,
    pub name: String,
    pub email: String,
    pub password: String,
}

#[derive(Deserialize)]
pub struct NewUser {
    pub name: String,
    pub email: String,
    pub password: String,
}

#[derive(Deserialize)]
pub struct UpdateUser {
    pub name: Option<String>,
    pub email: Option<String>,
    pub password: Option<String>,
}

Configuração das rotas

No mesmo arquivo, configure as rotas para CRUD:

use actix_web::{web, HttpResponse, Responder};
use crate::database::connection;
use bcrypt::{hash, verify};

pub async fn get_users(pool: web::Data<connection::PgPool>) -> impl Responder {
    let users = sqlx::query_as!(User, "SELECT * FROM users")
        .fetch_all(pool.get_ref())
        .await
        .unwrap();

    HttpResponse::Ok().json(users)
}

pub async fn create_user(
    pool: web::Data<connection::PgPool>,
    new_user: web::Json<NewUser>,
) -> impl Responder {
    let hashed_password = hash(&new_user.password, 4).unwrap();

    sqlx::query!(
        "INSERT INTO users (name, email, password) VALUES ($1, $2, $3)",
        new_user.name,
        new_user.email,
        hashed_password
    )
    .execute(pool.get_ref())
    .await
    .unwrap();

    HttpResponse::Created().finish()
}

pub async fn update_user(
    pool: web::Data<connection::PgPool>,
    user_id: web::Path<i32>,
    updated_user: web::Json<UpdateUser>,
) -> impl Responder {
    if let Some(name) = &updated_user.name {
        sqlx::query!(
            "UPDATE users SET name = $1 WHERE id = $2",
            name,
            *user_id
        )
        .execute(pool.get_ref())
        .await
        .unwrap();
    }

    if let Some(email) = &updated_user.email {
        sqlx::query!(
            "UPDATE users SET email = $1 WHERE id = $2",
            email,
            *user_id
        )
        .execute(pool.get_ref())
        .await
        .unwrap();
    }

    if let Some(password) = &updated_user.password {
        let hashed_password = hash(password, 4).unwrap();
        sqlx::query!(
            "UPDATE users SET password = $1 WHERE id = $2",
            hashed_password,
            *user_id
        )
        .execute(pool.get_ref())
        .await
        .unwrap();
    }

    HttpResponse::Ok().finish()
}

pub async fn delete_user(
    pool: web::Data<connection::PgPool>,
    user_id: web::Path<i32>,
) -> impl Responder {
    sqlx::query!("DELETE FROM users WHERE id = $1", *user_id)
        .execute(pool.get_ref())
        .await
        .unwrap();

    HttpResponse::NoContent().finish()
}

No src/services/mod.rs, importe:

pub mod user;

E no main.rs, conecte as rotas:

mod database;
mod services;

use actix_web::web;
use services::user;
use database::connection;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv().ok();

    let pool = connection::connect().await;

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(pool.clone()))
            .service(
                web::scope("/users")
                    .route("", web::get().to(user::get_users))
                    .route("", web::post().to(user::create_user))
                    .route("/{id}", web::put().to(user::update_user))
                    .route("/{id}", web::delete().to(user::delete_user))
            )
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Testando o CRUD completo

Inicie o servidor:

cargo run

No Postman ou Thunder Client, teste as rotas:

  1. GET /users: Lista todos os usuários.
  2. POST /users: Cria um novo usuário com name, email e password no corpo da requisição.
  3. PUT /users/{id}: Atualiza os dados de um usuário com base no ID.
  4. DELETE /users/{id}: Apaga um usuário pelo ID.

Conclusão

Rust API com Actix é uma escolha poderosa pra quem quer desempenho e segurança. Apesar da curva de aprendizado, vale muito a pena. Para mais dicas sobre Rust, confira a documentação oficial do Actix.

Se prepara pra impressionar nas entrevistas ou simplesmente pra dizer adeus às dores de cabeça com bugs de concorrência. E lembra: na próxima vez que alguém falar que Rust é difícil, manda esse artigo de presente!

Angular performance Angular Signals Apps Multiplataforma automação de tarefas Backend Boas Práticas boas práticas Git controle de versão desenvolvedores desenvolvimento backend Desenvolvimento de Software Desenvolvimento Frontend Desenvolvimento Mobile Desenvolvimento Web desenvolvimento ágil devops dicas para devs escalabilidade ferramentas de Git ferramentas de programação Front-end Git Hooks integração contínua inteligência artificial JavaScript Linguagens de Programação Media Queries mercado de tecnologia Mercado de Trabalho Tech Node.js produtividade dev Programação Programação Orientada a Objetos programação para iniciantes programação reativa Python React React Suspense Rust Tecnologia Trunk-Based Development web development workflow Git workflows Git

Publicar comentário

O que temos aqui?