Introdução ao GraphQL API em Rust
O desenvolvimento web moderno exige alta performance e flexibilidade ao trabalhar com dados. Rust é uma linguagem de programação de sistemas, conhecida por sua velocidade e segurança de memória, enquanto GraphQL é uma poderosa linguagem de consulta que permite aos clientes obter apenas os dados necessários. A combinação dessas tecnologias resulta em APIs incrivelmente rápidas e convenientes.
Neste tutorial, criaremos uma API GraphQL completa em Rust usando os populares crates Juniper (para implementação do GraphQL) e Actix-web (para o servidor HTTP). Você aprenderá como configurar o projeto, definir o schema, implementar resolvers e processar requisições.
O artigo é voltado para desenvolvedores que já conhecem os fundamentos do Rust e os conceitos básicos de APIs web. Usaremos a edição Rust 2021 e as versões estáveis mais recentes das dependências.
Passo 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-apiEm seguida, abra o arquivo Cargo.toml e adicione as seguintes dependências:
[dependencies]actix-web = "4"actix-rt = "2"juniper = "0.16"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }- actix-web — framework web assíncrono para Rust.
- juniper — biblioteca para implementar GraphQL em Rust.
- serde — para serialização/desserialização de dados.
- tokio — runtime assíncrono, necessário para o Actix.
Após atualizar as dependências, execute cargo build para garantir que tudo seja instalado corretamente.
Passo 2: Definição do modelo de dados e do schema GraphQL
Como exemplo, criaremos um modelo simples "Livro" com os campos: id, título, autor e ano de publicação. Crie o arquivo src/models.rs:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]pub struct Book { pub id: i32, pub title: String, pub author: String, pub year: i32,}Agora, vamos definir o schema GraphQL. Crie o arquivo src/graphql.rs:
use juniper::{FieldResult, GraphQLObject, GraphQLInputObject, RootNode};use crate::models::Book;
// Tipo GraphQL para livro (saída)#[derive(GraphQLObject)]#[graphql(description = "Livro")]pub struct BookGQL { pub id: i32, pub title: String, pub author: String, pub year: i32,}
// Tipo de entrada para criar um livro#[derive(GraphQLInputObject)]#[graphql(description = "Dados de entrada para um novo livro")]pub struct NewBook { pub title: String, pub author: String, pub year: i32,}
// "Banco de dados" em memóriause std::sync::Mutex;lazy_static::lazy_static! { static ref BOOKS: Mutex<Vec<Book>> = Mutex::new(vec![ Book { id: 1, title: "Rust em Ação".to_string(), author: "Tim McNamara".to_string(), year: 2021 }, Book { id: 2, title: "Programação em Rust".to_string(), author: "Jim Blandy".to_string(), year: 2020 }, ]);}
// Consulta raiz (Query)pub struct QueryRoot;
#[juniper::object]impl QueryRoot { fn books() -> FieldResult<Vec<BookGQL>> { let books = BOOKS.lock().unwrap(); Ok(books.iter().map(|b| BookGQL { id: b.id, title: b.title.clone(), author: b.author.clone(), year: b.year, }).collect()) }
fn book(id: i32) -> FieldResult<BookGQL> { let books = BOOKS.lock().unwrap(); let book = books.iter().find(|b| b.id == id) .ok_or_else(|| juniper::FieldError::new("Livro não encontrado", graphql_value!({"code": "NOT_FOUND"})))?; Ok(BookGQL { id: book.id,
```