0Pricing
Learn Rust Coding · Lección

Endpoints y modelos

Rutas y datos

Endpoints y modelos es una lección gratuita de Learn Rust Coding en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Learn Rust Coding, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Learn Rust Coding incluye 4 lecciones en total.

Partes de esta lección aún no han sido traducidas y se muestran en inglés.

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.

Preguntas frecuentes

¿La lección «Endpoints y modelos» es gratis?

Sí — el texto completo de «Endpoints y modelos» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Learn Rust Coding, actualiza a CoddyKit PRO. El curso de Learn Rust Coding incluye 4 lecciones en total.

¿Qué aprenderé en «Endpoints y modelos»?

Rutas y datos Practicas Learn Rust Coding con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Learn Rust Coding?

No se requiere experiencia previa. Learn Rust Coding en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Endpoints y modelos»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Learn Rust Coding?

Sí. Cada lección de Learn Rust Coding incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Configuración del proyecto
  2. Endpoints y modelos
  3. Integración con la base de datos
  4. Pruebas de la API
← Volver a Learn Rust Coding