Rust - Criando o projeto de estudos de backend com Axum
Vamos começar a nossa jornada criando um projeto simples em Rust usando o Axum para criar APIs. Nesta primeira parte, vou demonstrar como criar o projeto, instalar os componentes necessários, configurar a inicialização do servidor e as primeiras rotas. A ideia é, a cada semana, adicionar novas funcionalidades ao projeto, então certas decisões podem parecer estranhas em um primeiro momento, pois estarei utilizando uma forma mais didática.
Criando o projeto
Para criar o projeto, usaremos o Cargo, que é o gerenciador de pacotes do Rust. Para iniciar o projeto, execute o comando:
cargo new axum_api_project
cd axum_api_project
Instalando os pacotes necessários
Uma vez dentro do projeto, vamos instalar os pacotes com o cargo add
:
cargo add axum
cargo add tokio --features full
cargo add tower
cargo add serde --features derive
cargo add serde_json
Crates utilizadas:
axum
: Responsável por gerenciar a API.tokio
: Responsável por executar funções assíncronas. O parâmetrofeatures full
indica que queremos todos os componentes.tower
: Gerencia middlewares para a aplicação.serde
: Responsável pela serialização e desserialização de valores e estruturas. Neste momento, usaremos apenas o componentederive
.serde_json
: Responsável por serializar e desserializar JSON.
Criando o projeto
No main.rs
, primeiro precisamos importar os pacotes necessários:
use axum::{routing::{get, post}, Json, Router};
use serde::{Deserialize, Serialize};
Explicação do código:
Router
: Responsável por definir e gerenciar as rotas da API.routing::{get, post}
: Importa os métodos para criar rotas do tipoGET
ePOST
.Json
: Utilizado para converter automaticamente JSON em structs Rust (desserialização) e structs Rust em JSON (serialização).Deserialize
: Permite que uma estrutura Rust seja criada a partir de um JSON recebido.Serialize
: Permite que uma estrutura Rust seja convertida em JSON ao ser enviada como resposta.
Agora, configuramos o projeto para utilizar assíncronismo, marcando a função main
com os parâmetros do tokio
:
#[tokio::main]
fn main() {
println!("Hello, world!");
}
Agora, vamos criar a função para a rota GET
, que irá retornar a string Hello, World!
:
async fn hello_world() -> &'static str {
"Hello, World!"
}
Utilizamos async
para especificar que a função é assíncrona. Em Rust, quando queremos criar textos fixos no código, eles não são interpretados como String
, mas sim como &str
. Como esse valor não pode ser alterado e sempre será o mesmo, utilizamos 'static
para garantir que ele tenha um tempo de vida estático.
Agora, vamos criar uma estrutura para utilizarmos no POST
:
#[derive(Serialize, Deserialize)]
struct User {
name: String,
}
Aqui criamos uma struct
simples com somente o parâmetro name
. APIs não recebem e nem retornam structs
, mas sim JSON
. Assim, precisamos transformar (deserializar
) uma String JSON
em struct User
ao receber uma requisição e, ao responder, transformar (serializar
) uma struct User
em JSON
. Para isso, utilizamos #[derive(Serialize, Deserialize)]
.
Agora, precisamos criar a função que será responsável pelo POST
:
async fn post_user(Json(user): Json<User>) -> Json<User> {
Json(User {
name: format!("Hello: {}", user.name),
})
}
Assim como a outra, essa função também é assíncrona. O parâmetro de entrada Json(user)
usa Json<T>
, que facilita a conversão automática de JSON para a estrutura Rust (User
). No retorno, utilizamos novamente Json<T>
para serializar a struct User
de volta para JSON.
Dentro da função, criamos uma nova estrutura User
, baseada nos valores recebidos, mas alterando o parâmetro name
. A estrutura é retornada dentro de Json()
para que seja serializada corretamente.
Configurando e executando o servidor
Agora vamos configurar e iniciar o servidor Axum:
#[tokio::main]
fn main() {
let app = Router::new()
.route("/", get(hello_world))
.route("/", post(post_user));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Explicação do código:
let app = Router::new()
: Criamos a configuração do projeto para trabalhar como uma API e utilizar o sistema de rotas do Axum..route("/", get(hello_world)).route("/", post(post_user))
: Configuramos as rotas. Os métodosGET
ePOST
podem ser acessados na raiz / da API. Aqui não há conflito de endereço, pois os verbos HTTP são diferentes.let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap()
: Configuramos o listener assíncrono com Tokio. “0.0.0.0:3000” indica que o servidor aceitará conexões de qualquer IP na porta 3000.axum::serve(listener, app).await.unwrap()
: Iniciamos o servidor Axum utilizando as configurações delistener
erouters
.
Executando a API
Para executar a API, use:
cargo run
Para testar as rotas, você pode utilizar o cURL
:
Testando a rota GET
curl -X 'GET' 'http://127.0.0.1:3000/'
Você deverá ver o retorno:
Hello, World!
Testando a rota POST
curl -X 'POST' 'http://127.0.0.1:3000/' -H 'Content-Type: application/json' -d '{"name": "Meu Nome"}'
Você deverá ver o retorno:
{"name":"Hello: Meu Nome"}
Próximos passos
Isso é o suficiente para você começar a entender sobre APIs com Rust e Axum. Na próxima semana, irei implementar o uso de variáveis de ambiente. Apesar de ser algo básico, é muito importante no desenvolvimento de uma API para guardar informações de configuração de forma padronizada.
Obrigado por seguir o conteúdo e fique com Deus!
🔗 Link para a postagem no Linkedin
🔗 Confira o código no GitHub.