programmier-anfang()

Rust Web-API mit Axum und Tokio in 8 Schritten erstellen: Ein praktischer Leitfaden fuer Einsteiger

Sophie Reinhardt

Sophie Reinhardt

Senior Rust-Entwicklerin · 21. April 2026 · 14 Min. Lesezeit

Das Wichtigste in Kuerze

  • Dieser Leitfaden fuehrt Sie in acht klaren Schritten von der Rust-Installation bis zur live gehosteten API auf Fly.io - ohne Vorkenntnisse in Rust.
  • Sie nutzen den Axum-Framework auf Basis des bewaehrten Tokio-Async-Runtimes und binden PostgreSQL ueber sqlx an.
  • Sie lernen, wie Sie JSON mit Serde, JWT-Authentifizierung und Middleware sauber integrieren - produktionsreif.
  • Zeitbedarf: rund vier Stunden konzentrierte Arbeit. Vorkenntnisse in einer anderen Programmiersprache genuegen.

Rust hat sich in den letzten Jahren von einer Nischensprache fuer Systemprogrammierer zu einer Mainstream-Option fuer Web-APIs entwickelt. Unternehmen wie Discord, Cloudflare, AWS und in Deutschland 1&1, Delivery Hero und einige Fintechs setzen Rust produktiv ein. Die Kombination aus Axum als Web-Framework und Tokio als Async-Runtime gilt 2026 als der De-facto-Standard fuer neue Projekte. In diesem Leitfaden bauen Sie Schritt fuer Schritt eine produktionsreife API - mit JSON, PostgreSQL, Authentifizierung und Cloud-Deployment.

DER 8-SCHRITTE-WEG ZUR RUST-API1Install2cargo new3Axum4Routen5JSON6sqlx/PG7Auth8DeployVon lokaler Installation bis Cloud-LiveDurchschnittliche Bearbeitungszeit: 4 Stunden

Schritt 1: Rust und Cargo installieren

Rust wird ueber den offiziellen Toolchain-Manager rustup installiert. Auf Linux und macOS genuegt ein Einzeiler im Terminal, auf Windows laden Sie den Installer von rustup.rs. Rustup installiert parallel den Compiler rustc, den Paketmanager cargo und die Dokumentations-Suite.

# Linux und macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Installation pruefen
rustc --version
cargo --version

Erwartet wird eine Ausgabe wie rustc 1.86.0 (2026-03-14). Sollte cargo nach der Installation nicht sofort verfuegbar sein, laden Sie die Shell mit source $HOME/.cargo/env neu. Rust bringt mit Cargo das beste Build- und Dependency-System aller Systemsprachen, was die folgenden Schritte erheblich vereinfacht.

Schritt 2: Projekt initialisieren

Legen Sie ein neues Cargo-Projekt an. Wir waehlen den Namen anfang-api, sie koennen beliebig umbenennen.

cargo new anfang-api --bin
cd anfang-api
cargo run

Nach cargo run sollten Sie Hello, world! sehen. Die Projektstruktur ist minimal: Cargo.toml als Manifest, src/main.rs als Einstiegspunkt. Committen Sie das sofort in Git - alle weiteren Schritte bauen darauf auf.

Profi-Tipp

Installieren Sie zusaetzlich cargo-watch mit cargo install cargo-watch. Damit laeuft waehrend der Entwicklung cargo watch -x run, was bei jeder Dateiaenderung automatisch neu kompiliert und startet. Das verkuerzt die Feedback-Schleife deutlich und ist fuer Rust besonders wertvoll, weil der Compiler-Durchlauf laenger dauert als bei interpretierten Sprachen.

Schritt 3: Axum und Tokio hinzufuegen

Oeffnen Sie Cargo.toml und fuegen Sie im [dependencies]-Block die folgenden Eintraege hinzu. Wir nutzen die aktuellen Stable-Versionen zum Stand April 2026.

[dependencies]
axum = "0.7"
tokio = { version = "1.38", features = ["full"] }
tower = "0.5"
tower-http = { version = "0.5", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

Die Flag features = ["full"] bei Tokio aktiviert alle Sub-Module - fuer groessere Projekte sollten Sie spaeter feingranularer werden, um Compile-Zeit zu sparen. tower liefert das Middleware-Modell, tracing ist der De-facto-Standard fuer strukturierte Logs in Rust.

Schritt 4: Erste Route implementieren

Ersetzen Sie den Inhalt von src/main.rs durch den folgenden Code. Wir definieren einen minimalen Axum-Router mit zwei Routen: / fuer einen Gruss und /health fuer einen Health-Check.

use axum::{routing::get, Router};
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let app = Router::new()
        .route("/", get(root))
        .route("/health", get(health));

    let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    tracing::info!("anfang-api laeuft auf {}", addr);
    axum::serve(listener, app).await.unwrap();
}

async fn root() -> &'static str {
    "Willkommen bei der anfang-api"
}

async fn health() -> &'static str {
    "ok"
}

Starten Sie mit cargo run, dann pruefen Sie in einem zweiten Terminal mit curl http://localhost:3000/health. Sie erhalten ok. Gratulation - Sie haben Ihre erste Rust-API am Laufen. Achten Sie auf die Kompilationszeit: Beim ersten Durchlauf werden alle Dependencies kompiliert, spaetere inkrementelle Builds dauern nur wenige Sekunden.

Schritt 5: JSON mit Serde einfuehren

Fuer echte APIs brauchen Sie strukturierte Request- und Response-Bodies. Serde ist der Standard fuer Serialisierung in Rust und integriert sich nahtlos in Axum.

# Cargo.toml ergaenzen
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Erweitern Sie src/main.rs um eine POST-Route, die JSON annimmt und zurueckgibt:

use axum::{Json, routing::{get, post}};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateTask { title: String }

#[derive(Serialize)]
struct Task { id: u32, title: String, done: bool }

async fn create_task(Json(payload): Json<CreateTask>) -> Json<Task> {
    Json(Task { id: 1, title: payload.title, done: false })
}

// Router ergaenzen:
// .route("/tasks", post(create_task))

Testen Sie mit curl -X POST http://localhost:3000/tasks -H 'Content-Type: application/json' -d '{"title":"Rust lernen"}'. Serde validiert automatisch, dass das Feld title vorhanden ist. Fehlt es, antwortet Axum mit HTTP 422 und einer klaren Fehlermeldung.

Serde war fuer mich der Moment, in dem Rust fuer Web-APIs Sinn ergab. Keine manuellen JSON-Parser, keine Type-Casting-Unfaelle - der Compiler weiss, was rein- und rausgeht. — Ein Senior-Entwickler aus Berlin, 2026

Schritt 6: Datenbank mit sqlx und PostgreSQL

In der Produktion wollen Sie Daten persistieren. Wir nutzen sqlx, weil die Library zu Compile-Zeit gegen die Datenbank pruefen kann, ob Ihre SQL-Queries syntaktisch und schema-technisch korrekt sind. Das ist ein einzigartiges Rust-Feature.

# Cargo.toml ergaenzen
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "macros", "migrate", "uuid", "chrono"] }
uuid = { version = "1.8", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }

# sqlx-cli separat installieren
cargo install sqlx-cli --no-default-features --features postgres

Legen Sie eine PostgreSQL-Instanz lokal per Docker an: docker run --name pg -e POSTGRES_PASSWORD=dev -p 5432:5432 -d postgres:16. Exportieren Sie DATABASE_URL=postgres://postgres:dev@localhost/anfang, dann sqlx database create und sqlx migrate add create_tasks. Den generierten SQL-Migrationsordner fuellen Sie mit:

-- migrations/<timestamp>_create_tasks.sql
CREATE TABLE tasks (
    id UUID PRIMARY KEY,
    title TEXT NOT NULL,
    done BOOLEAN NOT NULL DEFAULT false,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Anschliessend sqlx migrate run. In main.rs initialisieren Sie einen Connection-Pool und teilen ihn via State-Extractor:

use sqlx::postgres::PgPoolOptions;
use axum::extract::State;

#[derive(Clone)]
struct AppState { db: sqlx::PgPool }

// In main():
let db = PgPoolOptions::new()
    .max_connections(5)
    .connect(&std::env::var("DATABASE_URL").unwrap())
    .await.unwrap();

let state = AppState { db };

// Router mit .with_state(state)

Queries nutzen das sqlx::query!-Makro fuer Compile-Time-Checks. Das ist der Schritt, bei dem die meisten Einsteiger einen Aha-Moment haben - Tippfehler in SQL werden zu Kompilierfehlern, nicht zu Laufzeit-Crashes.

Schritt 7: Middleware und Authentifizierung

Eine produktive API braucht Authentifizierung. Wir implementieren JWT-basierte Authentifizierung als Tower-Middleware.

# Cargo.toml ergaenzen
jsonwebtoken = "9.3"
axum-extra = { version = "0.9", features = ["typed-header"] }

Legen Sie eine Middleware-Funktion an, die den Authorization-Header prueft und den Token decodiert. Im Fehlerfall wird HTTP 401 zurueckgegeben.

use axum::{middleware, extract::Request, http::StatusCode, response::Response};
use jsonwebtoken::{decode, DecodingKey, Validation};

#[derive(Debug, Deserialize)]
struct Claims { sub: String, exp: usize }

async fn auth_middleware(mut req: Request, next: middleware::Next) -> Result<Response, StatusCode> {
    let token = req.headers()
        .get("authorization")
        .and_then(|v| v.to_str().ok())
        .and_then(|s| s.strip_prefix("Bearer "))
        .ok_or(StatusCode::UNAUTHORIZED)?;

    let secret = std::env::var("JWT_SECRET").map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    let data = decode::<Claims>(
        token,
        &DecodingKey::from_secret(secret.as_bytes()),
        &Validation::default(),
    ).map_err(|_| StatusCode::UNAUTHORIZED)?;

    req.extensions_mut().insert(data.claims);
    Ok(next.run(req).await)
}

// Router: .route_layer(middleware::from_fn(auth_middleware))

Nur die Routen, die hinter dem route_layer liegen, verlangen Authentifizierung. Ein Login-Endpoint kann selbst einen Token mit jsonwebtoken::encode ausstellen. Produktiv wuerden Sie das Secret aus einem Vault oder Fly.io-Secrets laden, niemals aus .env-Dateien in Git.

Experten-Meinung

Wenn Sie Rust zum ersten Mal produktiv einsetzen, unterschaetzen Sie nicht den Sprung von kompiliert zu funktional korrekt. Rust-Code, der kompiliert, ist fast immer speicher- und threadsicher - aber nicht zwangslaeufig logisch korrekt. Investieren Sie von Anfang an in Integration-Tests. Axum liefert mit axum::body::Body und tower::ServiceExt::oneshot sehr saubere Primitive, um Routen ohne Netzwerk-Round-Trip zu testen. Wenn Sie erfahrene Rust-Entwickler einstellen wollen, pruefen Sie unbedingt die Test-Kultur im Interview - unser Leitfaden fuer technische Interviews hilft dabei.

Schritt 8: Deployment auf Fly.io mit Docker

Der letzte Schritt ist das Deployment. Wir nutzen Fly.io, weil die Plattform Docker-Images ohne Umwege startet, globale Regionen anbietet und eine kostenlose Einstiegsstufe hat. Fuer europaeische Nutzer waehlen wir die Region fra (Frankfurt).

Legen Sie ein Dockerfile im Projekt-Root an. Der Build erfolgt in zwei Stufen, damit das finale Image schlank bleibt:

# Dockerfile
FROM rust:1.86-slim AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates libssl3 && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/anfang-api /usr/local/bin/
EXPOSE 3000
CMD ["anfang-api"]

Installieren Sie die Fly-CLI mit curl -L https://fly.io/install.sh | sh, dann:

fly auth login
fly launch --name anfang-api --region fra --no-deploy
fly postgres create --name anfang-db --region fra
fly postgres attach anfang-db --app anfang-api
fly secrets set JWT_SECRET=<starker-zufallswert>
fly deploy

Nach einigen Minuten laeuft Ihre API unter https://anfang-api.fly.dev. Fly.io skaliert automatisch nach Bedarf, unterstuetzt 0-to-N-Skalierung und laesst Ihre App schlafen, wenn keine Requests kommen - das senkt die Kosten auf wenige Euro im Monat fuer Nebenprojekte.

Suchen Sie Rust-Entwickler fuer Ihr naechstes Projekt?

Unser deutsches Netzwerk umfasst vorvalidierte Rust-Entwickler mit Axum-, Actix-Web- und Tokio-Erfahrung. Festanstellung oder Freelance. Matching in 48 Stunden.

Jetzt Entwickler finden

Haeufige Fehler und wie Sie sie vermeiden

Rust-Neulinge stolpern oft ueber dieselben drei Themen. Erstens: die Borrow-Checker-Fehler bei geteilten Zustaenden. Wenn Sie einen Datenbank-Pool in mehreren Handler benoetigen, muessen Sie ihn in Arc oder via State kapseln, nicht direkt als Referenz uebergeben. Zweitens: asynchrone Iteratoren. Statt for row in rows brauchen Sie while let Some(row) = stream.next().await. Drittens: fehlerhafter Umgang mit Result. Nutzen Sie das ?-Operator und definieren Sie einen eigenen Fehler-Typ, der IntoResponse implementiert - das haelt Handler kurz und die Fehlerantworten konsistent.

Ein haeufig unterschaetztes Thema ist das Logging. Richten Sie von Anfang an tracing mit RUST_LOG=info ein, sonst sind Production-Incidents schwer zu debuggen. Fly.io streamt alle Logs automatisch an fly logs, die Integration mit Grafana Loki oder Datadog ist ueber Standard-Adapter moeglich.

Experten-Meinung

Die deutsche Rust-Community ist 2026 stark gewachsen. Die Rust Cologne-Meetups haben regelmaessig 80 bis 120 Teilnehmer, in Berlin und Muenchen sind es mehr. Fuer Arbeitgeber heisst das: gut gepflegte Talent-Pools, aber auch starke Konkurrenz um Senior-Profile. Junior-Rust-Entwickler sind selten, weil der Einstieg viel Zeit kostet - ein strukturiertes Onboarding-Programm mit Mentorship ist der Schluessel. Im internationalen Vergleich lohnt der Blick auf den UAE-Markt ueber HireDeveloper.ae und den Singapurer Markt ueber HireDeveloper.sg, wo Remote-Talent zu attraktiven Konditionen verfuegbar ist.

Performance-Benchmarks: Was zu erwarten ist

Ein einfacher /health-Endpoint auf einer Fly.io-Shared-CPU mit 256 MB RAM liefert typischerweise 40 000 bis 70 000 Requests pro Sekunde bei p99-Latenzen unter 5 Millisekunden. Mit Datenbankzugriff sinkt der Durchsatz auf 5 000 bis 15 000 Requests pro Sekunde - was fuer nahezu jede B2B-Anwendung mehr als ausreichend ist. Im Vergleich dazu liefern Node.js-Express-Apps auf derselben Hardware typischerweise 8 000 bis 15 000 Requests pro Sekunde ohne DB, Python-FastAPI etwa 3 000 bis 6 000. Der Memory-Fussabdruck von Axum unter Last liegt bei 20 bis 60 MB, verglichen mit 100 bis 200 MB fuer vergleichbare Node- oder Python-Stacks.

Fuer deutsche Unternehmen, die in die Cloud skalieren, ergibt das mittelbar Kostenvorteile: weniger Container, kleinere Instanzen, weniger Auto-Scaling-Events. Die total cost of ownership liegt bei typischen Mittelstands-APIs 30 bis 50 Prozent unter einem vergleichbaren Node- oder Python-Stack - wenn Sie das Entwicklungs-Know-how im Haus haben. Lesen Sie dazu auch unseren Artikel ueber Rust- und KI-Entwickler in Deutschland 2026.

Naechste Schritte: Vom Tutorial zur Produktionsreife

Nach diesem Leitfaden haben Sie die Grundlagen. Fuer echte Produktion fehlen noch: strukturierte Fehlerbehandlung mit thiserror, Metriken mit prometheus, OpenTelemetry-Tracing, Rate Limiting per tower_governor und ein sauber konfiguriertes tower-http::cors-Layer. Alle diese Bausteine integrieren sich in denselben Router, ohne die Handler-Signaturen zu verschmutzen - das ist die eigentliche Staerke von Axum: Produktionskomplexitaet ohne Framework-Zentrismus.

Haeufig gestellte Fragen

Warum Rust mit Axum und Tokio fuer eine Web-API?

Rust bietet Speichersicherheit ohne Garbage Collection, Axum auf Tokio liefert 50 000 bis 150 000 Requests/Sekunde bei minimalem Memory-Fussabdruck. Fuer deutsche Unternehmen zunehmend ein Kostenfaktor in der Cloud.

Ist Rust schwer zu lernen?

Steilere Kurve als Python oder Go wegen Ownership und Lifetimes. Mit C++-, Java- oder TypeScript-Vorkenntnissen vier bis sechs Wochen bis zur Produktivitaet, fuer Einsteiger drei bis vier Monate.

Welche Alternativen zu Axum gibt es?

Actix-Web (etabliert, sehr performant), Rocket (angenehme DX, weniger async-nativ). Axum ist 2026 die beliebteste Wahl fuer neue Projekte.

Wie viel kostet Fly.io-Hosting?

Einfache API auf 256-MB-Machine rund 2 bis 5 USD/Monat. Produktive Konfiguration mit zwei Instanzen und gemanagter PostgreSQL typischerweise 15 bis 35 USD/Monat in Region fra (Frankfurt).

Planen Sie einen Rust-Stack in 2026?

Unser Team unterstuetzt Sie bei der Personalplanung: Senior Rust-Entwickler, Fullstack-Profile mit Rust-Backend und TypeScript-Frontend, Freelancer fuer kurzfristige Spikes. Kostenloses Erstgespraech in 48 Stunden.

Beratungstermin vereinbaren

Aehnliche Artikel