Introducción a GraphQL API en Rust
El desarrollo web moderno requiere alto rendimiento y flexibilidad al trabajar con datos. Rust es un lenguaje de programación de sistemas, conocido por su velocidad y seguridad de memoria, mientras que GraphQL es un potente lenguaje de consultas que permite a los clientes obtener solo los datos necesarios. La combinación de estas tecnologías proporciona API increíblemente rápidas y convenientes.
En este tutorial crearemos una API GraphQL completa en Rust utilizando los populares crates Juniper (para la implementación de GraphQL) y Actix-web (para el servidor HTTP). Aprenderá cómo configurar el proyecto, definir el esquema, implementar resolvers y procesar consultas.
El artículo está dirigido a desarrolladores que ya están familiarizados con los fundamentos de Rust y los conceptos básicos de las API web. Usaremos Rust 2021 edition y las últimas versiones estables de las dependencias.
Paso 1: Configuración del proyecto y dependencias
Empecemos creando un nuevo proyecto de Rust. Abra la terminal y ejecute:
cargo new graphql-rust-apicd graphql-rust-apiLuego, abra el archivo Cargo.toml y agregue las siguientes dependencias:
[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 asíncrono para Rust.
- juniper — biblioteca para implementar GraphQL en Rust.
- serde — para serialización/deserialización de datos.
- tokio — runtime asíncrono, necesario para Actix.
Después de actualizar las dependencias, ejecute cargo build para asegurarse de que todo se instale correctamente.
Paso 2: Definición del modelo de datos y esquema GraphQL
Como ejemplo, crearemos un modelo simple "Libro" con los campos: id, título, autor y año de publicación. Crearemos el archivo 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,}Ahora definamos el esquema GraphQL. Crearemos src/graphql.rs:
use juniper::{FieldResult, GraphQLObject, GraphQLInputObject, RootNode};use crate::models::Book;
// Tipo GraphQL para el libro (salida)#[derive(GraphQLObject)]#[graphql(description = "Libro")]pub struct BookGQL { pub id: i32, pub title: String, pub author: String, pub year: i32,}
// Tipo de entrada para crear un libro#[derive(GraphQLInputObject)]#[graphql(description = "Datos de entrada para un nuevo libro")]pub struct NewBook { pub title: String, pub author: String, pub year: i32,}
// "Base de datos" en memoriause std::sync::Mutex;lazy_static::lazy_static! { static ref BOOKS: Mutex<Vec<Book>> = Mutex::new(vec![ Book { id: 1, title: "Rust en acción".to_string(), author: "Tim McNamara".to_string(), year: 2021 }, Book { id: 2, title: "Programación en Rust".to_string(), author: "Jim Blandy".to_string(), year: 2020 }, ]);}
// Consulta raíz (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("Libro no encontrado", graphql_value!({"code": "NOT_FOUND"})))?; Ok(BookGQL { id: book.id,
```