Einführung in die GraphQL API mit Rust
Moderne Webentwicklung erfordert hohe Leistung und Flexibilität bei der Arbeit mit Daten. Rust ist eine systemnahe Programmiersprache, bekannt für ihre Geschwindigkeit und Speichersicherheit, während GraphQL eine mächtige Abfragesprache ist, die es Clients ermöglicht, nur die benötigten Daten zu erhalten. Die Kombination dieser Technologien ergibt unglaublich schnelle und praktische APIs.
In diesem Tutorial erstellen wir eine vollständige GraphQL API mit Rust unter Verwendung der beliebten Crates Juniper (für die GraphQL-Implementierung) und Actix-web (für den HTTP-Server). Sie lernen, wie Sie das Projekt einrichten, das Schema definieren, Resolver implementieren und Anfragen verarbeiten.
Der Artikel richtet sich an Entwickler, die bereits mit den Grundlagen von Rust und den grundlegenden Konzepten von Web-APIs vertraut sind. Wir verwenden die Rust 2021 Edition und die neuesten stabilen Versionen der Abhängigkeiten.
Schritt 1: Projekteinrichtung und Abhängigkeiten
Beginnen wir mit der Erstellung eines neuen Rust-Projekts. Öffnen Sie ein Terminal und führen Sie Folgendes aus:
cargo new graphql-rust-apicd graphql-rust-apiÖffnen Sie dann die Datei Cargo.toml und fügen Sie die folgenden Abhängigkeiten hinzu:
[dependencies]actix-web = "4"actix-rt = "2"juniper = "0.16"serde = { version = "1.0", features = ["derive"] }tokio = { version = "1", features = ["full"] }- actix-web — asynchrones Web-Framework für Rust.
- juniper — Bibliothek zur Implementierung von GraphQL in Rust.
- serde — zur Serialisierung/Deserialisierung von Daten.
- tokio — asynchrone Laufzeitumgebung, die für Actix benötigt wird.
Führen Sie nach dem Aktualisieren der Abhängigkeiten cargo build aus, um sicherzustellen, dass alles korrekt installiert wird.
Schritt 2: Definition des Datenmodells und des GraphQL-Schemas
Als Beispiel erstellen wir ein einfaches Modell "Buch" mit den Feldern: id, Titel, Autor und Erscheinungsjahr. Erstellen Sie die Datei 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,}Definieren wir nun das GraphQL-Schema. Erstellen Sie src/graphql.rs:
use juniper::{FieldResult, GraphQLObject, GraphQLInputObject, RootNode};use crate::models::Book;
// GraphQL-Typ für Buch (Ausgabe)#[derive(GraphQLObject)]#[graphql(description = "Buch")]pub struct BookGQL { pub id: i32, pub title: String, pub author: String, pub year: i32,}
// Eingabetyp zum Erstellen eines Buches#[derive(GraphQLInputObject)]#[graphql(description = "Eingabedaten für ein neues Buch")]pub struct NewBook { pub title: String, pub author: String, pub year: i32,}
// "Datenbank" im Speicheruse std::sync::Mutex;lazy_static::lazy_static! { static ref BOOKS: Mutex<Vec<Book>> = Mutex::new(vec![ Book { id: 1, title: "Rust in Action".to_string(), author: "Tim McNamara".to_string(), year: 2021 }, Book { id: 2, title: "Programming Rust".to_string(), author: "Jim Blandy".to_string(), year: 2020 }, ]);}
// Stammabfrage (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("Buch nicht gefunden", graphql_value!({"code": "NOT_FOUND"})))?; Ok(BookGQL { id: book.id,