0Pricing
Learn Rust Coding · Lektion

Endpoints und Modelle

Routen und Daten

Endpoints und Modelle ist eine kostenlose Learn Rust Coding-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Learn Rust Coding-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Learn Rust Coding-Kurs umfasst insgesamt 4 Lektionen.

Teile dieser Lektion wurden noch nicht übersetzt und werden auf Englisch angezeigt.

Routes and Data Models

An API is defined by its endpoints (URLs plus HTTP methods) and the models (data shapes) flowing through them. In this lesson you will define request and response models with serde and wire up CRUD-style routes in Axum.

Defining a Model

A model is a plain Rust struct. Derive Serialize so it can become JSON in responses and Deserialize so it can be parsed from request bodies. The field names map directly to JSON keys.

use serde::{Serialize, Deserialize};

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

Separate Request and Response Types

Clients should not send the server-assigned id when creating a resource. Use a separate input struct for the request body and the full model for responses. This keeps the contract clear and prevents clients from setting fields they should not.

use serde::Deserialize;

#[derive(Deserialize)]
struct CreateTodo {
    title: String,
}
// The handler assigns the id and sets done = false.

GET: Listing Resources

A GET /todos handler returns the whole collection as a JSON array. It reads the shared state, clones the data out of the lock quickly, and wraps it in Json.

use axum::{extract::State, Json};
use std::sync::{Arc, Mutex};

type Store = Arc<Mutex<Vec<Todo>>>;
#[derive(Clone, serde::Serialize)]
struct Todo { id: u32, title: String, done: bool }

async fn list_todos(State(store): State<Store>) -> Json<Vec<Todo>> {
    let todos = store.lock().unwrap().clone();
    Json(todos)
}

POST: Creating a Resource

A POST /todos handler reads the JSON body with the Json extractor, assigns a new id, stores the item, and returns it with status 201 Created. The tuple (StatusCode, Json<T>) lets you set both status and body.

use axum::{extract::State, Json, http::StatusCode};

async fn create_todo(
    State(store): State<Store>,
    Json(input): Json<CreateTodo>,
) -> (StatusCode, Json<Todo>) {
    let mut todos = store.lock().unwrap();
    let id = todos.len() as u32 + 1;
    let todo = Todo { id, title: input.title, done: false };
    todos.push(todo.clone());
    (StatusCode::CREATED, Json(todo))
}

Path Parameters

To fetch one resource, capture part of the URL with a path parameter. Declare it in the route as /todos/{id} and extract it with the Path extractor. The type you ask for (here u32) is parsed automatically.

use axum::extract::{Path, State};
use axum::{Json, http::StatusCode};

async fn get_todo(
    State(store): State<Store>,
    Path(id): Path<u32>,
) -> Result<Json<Todo>, StatusCode> {
    let todos = store.lock().unwrap();
    match todos.iter().find(|t| t.id == id) {
        Some(t) => Ok(Json(t.clone())),
        None => Err(StatusCode::NOT_FOUND),
    }
}

Query Parameters

Filtering and pagination use the query string, like /todos?done=true. Capture it with the Query extractor into a Deserialize struct. Optional fields use Option so missing params are fine.

use axum::extract::{Query, State};
use axum::Json;
use serde::Deserialize;

#[derive(Deserialize)]
struct Filter { done: Option<bool> }

async fn filtered(
    State(store): State<Store>,
    Query(f): Query<Filter>,
) -> Json<Vec<Todo>> {
    let todos = store.lock().unwrap();
    let out = todos.iter()
        .filter(|t| f.done.map_or(true, |d| t.done == d))
        .cloned().collect();
    Json(out)
}

PUT and DELETE

Updating uses PUT /todos/{id} with a body; deleting uses DELETE /todos/{id}. Both look up the item by id and return 404 if it is missing. Delete typically returns 204 No Content on success.

use axum::extract::{Path, State};
use axum::http::StatusCode;

async fn delete_todo(
    State(store): State<Store>,
    Path(id): Path<u32>,
) -> StatusCode {
    let mut todos = store.lock().unwrap();
    let before = todos.len();
    todos.retain(|t| t.id != id);
    if todos.len() < before { StatusCode::NO_CONTENT }
    else { StatusCode::NOT_FOUND }
}

Wiring the Routes Together

Register every handler on the router. Group methods on shared paths: /todos handles list and create, while /todos/{id} handles fetch, update, and delete. Attach the shared store with .with_state.

use axum::{routing::get, Router};

fn build(store: Store) -> Router {
    Router::new()
        .route("/todos", get(list_todos).post(create_todo))
        .route("/todos/{id}",
            get(get_todo).delete(delete_todo))
        .with_state(store)
}

Validating Input

Never trust client data. Check it inside the handler and return 400 Bad Request when it is invalid. Here we reject an empty title before storing anything, keeping bad data out of the system.

use axum::{Json, extract::State, http::StatusCode};

async fn create_validated(
    State(store): State<Store>,
    Json(input): Json<CreateTodo>,
) -> Result<(StatusCode, Json<Todo>), StatusCode> {
    if input.title.trim().is_empty() {
        return Err(StatusCode::BAD_REQUEST);
    }
    let mut todos = store.lock().unwrap();
    let id = todos.len() as u32 + 1;
    let todo = Todo { id, title: input.title, done: false };
    todos.push(todo.clone());
    Ok((StatusCode::CREATED, Json(todo)))
}

Consistent Error Responses

Returning bare status codes works, but production APIs return a JSON error body too. A common approach is a custom error enum implementing IntoResponse, mapping each variant to a status and message. This gives clients predictable, machine-readable errors.

use axum::response::{IntoResponse, Response};
use axum::http::StatusCode;
use axum::Json;
use serde_json::json;

enum ApiError { NotFound, BadRequest(String) }

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let (status, msg) = match self {
            ApiError::NotFound => (StatusCode::NOT_FOUND, "not found".to_string()),
            ApiError::BadRequest(m) => (StatusCode::BAD_REQUEST, m),
        };
        (status, Json(json!({ "error": msg }))).into_response()
    }
}

Quick Check

Test your understanding of endpoints and models.

Recap

You defined endpoints and models:

  • Models are structs deriving Serialize/Deserialize; use separate input types.
  • Json, Path, Query, and State extract request data.
  • Map CRUD to GET/POST/PUT/DELETE with appropriate status codes.
  • Validate input and return 400 on bad data.
  • A custom error type implementing IntoResponse gives consistent JSON errors.

Häufig gestellte Fragen

Ist die Lektion „Endpoints und Modelle“ kostenlos?

Ja — der vollständige Text von „Endpoints und Modelle“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Learn Rust Coding-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Learn Rust Coding-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Endpoints und Modelle“?

Routen und Daten Du übst Learn Rust Coding mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Learn Rust Coding zu starten?

Keine Vorkenntnisse erforderlich. Learn Rust Coding auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.

Wie lange dauert die Lektion „Endpoints und Modelle“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Learn Rust Coding-Lektion Code schreiben und ausführen?

Ja. Jede Learn Rust Coding-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Projekteinrichtung
  2. Endpoints und Modelle
  3. Datenbankintegration
  4. Die API testen
← Zurück zu Learn Rust Coding