Introdução à API GraphQL em Rust
O desenvolvimento web moderno exige APIs flexíveis e performáticas. GraphQL é uma linguagem de consulta que permite aos clientes obter exatamente os dados de que precisam, sem redundância. Rust, por sua vez, oferece alta performance e segurança de memória. Neste tutorial, criaremos um servidor GraphQL simples, mas completo, em Rust usando a biblioteca async-graphql. Você aprenderá a configurar o projeto, definir tipos de dados, escrever resolvers e iniciar o servidor. O artigo é voltado para desenvolvedores iniciantes familiarizados com os fundamentos do Rust.
Configuração do projeto e instalação de dependências
Para começar, vamos criar um novo projeto Rust com o Cargo:
cargo new graphql-rust-tutorialcd graphql-rust-tutorialAbra o arquivo Cargo.toml e adicione as seguintes dependências:
[dependencies]async-graphql = "7.0"async-graphql-actix-web = "7.0"actix-web = "4"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }Explicando a escolha das bibliotecas:
- async-graphql — biblioteca principal para trabalhar com GraphQL em Rust.
- async-graphql-actix-web — integração com o framework web Actix-web.
- actix-web — servidor HTTP assíncrono popular.
- serde — para serialização/desserialização de dados.
- tokio — runtime assíncrono.
Execute cargo build para verificar se todas as dependências foram instaladas corretamente.
Definição do esquema GraphQL e resolvers
Vamos criar um modelo de dados simples — um livro. Definiremos a estrutura e os resolvers para consultas e mutações.
Criação do modelo de dados
Crie o arquivo src/models.rs e adicione o seguinte código:
use async_graphql::*;
#[derive(SimpleObject)]pub struct Book { pub id: ID, pub title: String, pub author: String,}
#[derive(InputObject)]pub struct NewBook { pub title: String, pub author: String,}Aqui, SimpleObject gera automaticamente o tipo GraphQL para a estrutura Book, e InputObject para os dados de entrada da mutação.
Criação do armazenamento e resolvers
Vamos criar o arquivo src/resolvers.rs, que conterá a lógica das consultas e mutações:
use std::sync::Mutex;use async_graphql::*;use crate::models::{Book, NewBook};
pub struct QueryRoot;
#[Object]impl QueryRoot { async fn books(&self) -> Vec<Book> { let storage = STORAGE.lock().unwrap(); storage.clone() }
async fn book(&self, id: ID) -> Option<Book> { let storage = STORAGE.lock().unwrap(); storage.iter().find(|b| b.id == id).cloned() }}
pub struct MutationRoot;
#[Object]impl MutationRoot { async fn add_book(&self, input: NewBook) -> Book { let mut storage = STORAGE.lock().unwrap(); let new_book = Book { id: ID::from(format!("book_{}", storage.len() + 1)), title: input.title, author: input.author, }; storage.push(new_book.clone()); new_book }}
lazy_static::lazy_static! { static ref STORAGE: Mutex<Vec<Book>> = Mutex::new(vec![]);}Observe: usamos Mutex para acesso thread-safe ao armazenamento. Em um projeto real, é melhor usar um banco de dados.
Montagem do esquema
Agora, vamos unir tudo no arquivo src/main.rs:
use actix_web::{web, App, HttpServer, HttpResponse, middleware};use async_graphql::{http::GraphiQLSource, EmptySubscription, Schema};use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};use std::sync::Arc;
mod models;mod resol