Introducción a GraphQL API en Rust
El desarrollo web moderno requiere API flexibles y de alto rendimiento. GraphQL es un lenguaje de consulta que permite a los clientes obtener exactamente los datos que necesitan, sin redundancia. Rust, por su parte, ofrece alto rendimiento y seguridad en la memoria. En este tutorial crearemos un servidor GraphQL simple pero completo en Rust utilizando la biblioteca async-graphql. Aprenderás a configurar el proyecto, definir tipos de datos, escribir resolutores y ejecutar el servidor. El artículo está dirigido a desarrolladores principiantes familiarizados con los fundamentos de Rust.
Configuración del proyecto e instalación de dependencias
Para empezar, crearemos un nuevo proyecto Rust con Cargo:
cargo new graphql-rust-tutorialcd graphql-rust-tutorialAbre el archivo Cargo.toml y agrega las siguientes dependencias:
[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"] }Explicamos la elección de las bibliotecas:
- async-graphql — la biblioteca principal para trabajar con GraphQL en Rust.
- async-graphql-actix-web — integración con el framework web Actix-web.
- actix-web — un popular servidor HTTP asíncrono.
- serde — para serialización/deserialización de datos.
- tokio — runtime asíncrono.
Ejecuta cargo build para asegurarte de que todas las dependencias estén instaladas correctamente.
Definición del esquema GraphQL y resolutores
Crearemos un modelo de datos simple: un libro. Definiremos la estructura y los resolutores para consultas y mutaciones.
Creación del modelo de datos
Crea el archivo src/models.rs y agrega el siguiente 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,}Aquí, SimpleObject genera automáticamente el tipo GraphQL para la estructura Book, y InputObject para los datos de entrada de la mutación.
Creación del almacenamiento y resolutores
Crearemos el archivo src/resolvers.rs, que contendrá la lógica de consultas y mutaciones:
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![]);}Ten en cuenta: usamos Mutex para acceso seguro a los hilos al almacenamiento. En un proyecto real, es mejor usar una base de datos.
Ensamblaje del esquema
Ahora unimos todo en el archivo 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