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 fullindica 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 tipoGETePOST.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étodosGETePOSTpodem 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 delistenererouters.
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.