GraphQL API com Rust: tutorial prático passo a passo
GraphQL é uma linguagem de consulta poderosa para APIs que permite aos clientes solicitarem apenas os dados de que precisam. Combinado com o desempenho e a segurança do Rust, você obtém uma solução ideal para serviços web de alta carga. Neste tutorial, criaremos uma API GraphQL completa do zero, usando a crate async-graphql.
Por que Rust? Ele oferece abstrações de custo zero, segurança de memória e velocidade impressionante. GraphQL em Rust é mais rápido que alternativas em JavaScript ou Python, o que é crítico para sistemas em produção.
1. Configuração do projeto e dependências
Vamos começar criando um novo projeto Rust. Abra o terminal e execute:
cargo new graphql_rust_apicd graphql_rust_apiAgora adicione as dependências necessárias no arquivo Cargo.toml:
[dependencies]async-graphql = "7.0"async-graphql-actix-web = "7.0"actix-web = "4"actix-cors = "0.7"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }Essas crates fornecem tudo o que é necessário: async-graphql — o núcleo do GraphQL, actix-web — o servidor web, serde — serialização de dados.
2. Criação do modelo de dados e esquema
Vamos definir um modelo simples para nossa API — livros. Criaremos as estruturas e o esquema GraphQL:
use async_graphql::*;use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, Serialize, Deserialize, SimpleObject)]struct Book { id: ID, title: String, author: String, year: i32,}
#[derive(Clone, Debug, Serialize, Deserialize, InputObject)]struct BookInput { title: String, author: String, year: i32,}Observe os atributos #[derive(SimpleObject)] e #[derive(InputObject)]. O primeiro gera automaticamente um tipo GraphQL para saída, o segundo para entrada de dados.
3. Implementação dos resolvers (Query e Mutation)
Resolvers são funções que processam as consultas. Vamos criar a estrutura de armazenamento e os resolvers:
use std::sync::Mutex;
struct QueryRoot;
#[Object]impl QueryRoot { async fn books(&self, ctx: &Context<'_>) -> Vec<Book> { let storage = ctx.data_unchecked::<Mutex<Vec<Book>>>(); storage.lock().unwrap().clone() }
async fn book(&self, ctx: &Context<'_>, id: ID) -> Option<Book> { let storage = ctx.data_unchecked::<Mutex<Vec<Book>>>(); storage.lock().unwrap().iter().find(|b| b.id == id).cloned() }}
struct MutationRoot;
#[Object]impl MutationRoot { async fn add_book(&self, ctx: &Context<'_>, input: BookInput) -> Book { let mut storage = ctx.data_unchecked::<Mutex<Vec<Book>>>() .lock().unwrap(); let book = Book { id: ID::from(storage.len() + 1), title: input.title, author: input.author, year: input.year, }; storage.push(book.clone()); book }}Usamos Mutex para acesso thread-safe aos dados. Em um projeto real, você conectaria um banco de dados, mas para este tutorial é a opção ideal.
4. Montagem e inicialização do servidor
O passo final é unir tudo em um servidor funcional. Vamos criar o ponto de entrada:
use actix_web::{guard, web, App, HttpServer, HttpRequest, HttpResponse};use actix_cors::Cors;use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
async fn index( schema: web::Data<Schema<QueryRoot, MutationRoot, EmptySubscription>>, req: GraphQLRequest,) -> GraphQLResponse { schema.execute(req.into_inner()).await.into()}
#[actix_web::main]async fn main() -> std::