0Pricing
Learn Rust Coding · Урок

Конечные точки и модели

Маршруты и данные

«Конечные точки и модели» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.

Части этого урока еще не переведены и отображаются на английском.

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.

Часто задаваемые вопросы

Урок «Конечные точки и модели» бесплатный?

Да — полный текст урока «Конечные точки и модели» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.

Чему я научусь в уроке «Конечные точки и модели»?

Маршруты и данные Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Learn Rust Coding?

Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Конечные точки и модели»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Learn Rust Coding?

Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Настройка проекта
  2. Конечные точки и модели
  3. Интеграция с базой данных
  4. Тестирование API
← Назад к Learn Rust Coding