엔드포인트와 모델
경로와 데이터
엔드포인트와 모델은(는) CoddyKit의 무료 Learn Rust Coding 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Learn Rust Coding 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Learn Rust Coding 강의에는 총 4개의 강의가 포함되어 있습니다.
경로와 데이터 모델
API는 엔드포인트(URL과 HTTP 메서드)와 그 사이를 오가는 모델(데이터 형태)로 정의됩니다. 이 레슨에서는 serde로 요청 및 응답 모델을 정의하고 Axum에 CRUD 방식의 경로를 연결합니다.
모델 정의하기
모델은 단순한 Rust 구조체입니다. 응답에서 JSON으로 변환할 수 있도록 Serialize를 파생하고, 요청 본문에서 파싱할 수 있도록 Deserialize를 파생하십시오. 필드 이름은 JSON 키에 직접 매핑됩니다.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone)]
struct Todo {
id: u32,
title: String,
done: bool,
}요청 및 응답 타입 분리하기
리소스를 생성할 때 클라이언트가 서버에서 할당하는 id를 보내서는 안 됩니다. 요청 본문에는 별도의 입력 구조체를 사용하고, 응답에는 전체 모델을 사용하십시오. 이렇게 하면 계약이 명확해지고 클라이언트가 설정해서는 안 되는 필드를 지정하는 일을 막을 수 있습니다.
use serde::Deserialize;
#[derive(Deserialize)]
struct CreateTodo {
title: String,
}
// The handler assigns the id and sets done = false.GET: 리소스 목록 조회
GET /todos 핸들러는 전체 컬렉션을 JSON 배열로 반환합니다. 공유 상태를 읽고, 잠금에서 데이터를 빠르게 복제해 꺼낸 다음 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: 리소스 생성
POST /todos 핸들러는 Json 추출기로 JSON 본문을 읽고, 새 id를 할당하고, 항목을 저장한 다음 상태 코드 201 Created와 함께 반환합니다. (StatusCode, Json<T>) 튜플을 사용하면 상태 코드와 본문을 모두 설정할 수 있습니다.
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))
}경로 매개변수
하나의 리소스를 가져오려면 경로 매개변수로 URL의 일부를 캡처하십시오. 경로에 /todos/{id}로 선언하고 Path 추출기로 추출합니다. 요청한 타입(여기서는 u32)으로 자동 파싱됩니다.
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),
}
}쿼리 매개변수
필터링과 페이지 매김에는 /todos?done=true와 같은 쿼리 문자열을 사용합니다. Query 추출기로 이를 Deserialize 구조체에 담아 추출하십시오. 선택적 필드에는 Option을 사용하므로 매개변수가 없어도 문제가 없습니다.
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 및 DELETE
업데이트에는 본문과 함께 PUT /todos/{id}를 사용하고, 삭제에는 DELETE /todos/{id}를 사용합니다. 두 작업 모두 id로 항목을 찾으며, 항목이 없으면 404를 반환합니다. 삭제에 성공하면 일반적으로 204 No Content를 반환합니다.
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 }
}경로 연결하기
모든 핸들러를 라우터에 등록하십시오. 공유 경로의 메서드를 그룹화합니다. /todos는 목록 조회와 생성을 처리하고, /todos/{id}는 조회, 업데이트 및 삭제를 처리합니다. .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)
}입력 검증하기
클라이언트 데이터를 절대 신뢰하지 마십시오. 핸들러 내부에서 확인하고 유효하지 않으면 400 Bad Request를 반환하십시오. 여기서는 저장하기 전에 빈 제목을 거부하여 잘못된 데이터가 시스템에 들어오지 않도록 합니다.
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)))
}일관된 오류 응답
상태 코드만 반환해도 작동하지만, 실제 API에서는 JSON 오류 본문도 함께 반환합니다. 일반적인 방법은 IntoResponse를 구현하는 사용자 정의 오류 열거형을 만들고 각 변형을 상태 코드와 메시지에 매핑하는 것입니다. 이렇게 하면 클라이언트가 예측 가능하고 기계가 읽을 수 있는 오류를 받습니다.
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()
}
}빠른 확인
엔드포인트와 모델에 대한 이해도를 확인해 보십시오.
복습
엔드포인트와 모델을 정의했습니다.
- 모델은
Serialize/Deserialize를 파생하는 구조체이며, 입력에는 별도의 타입을 사용합니다. Json,Path,Query,State가 요청 데이터를 추출합니다.- 적절한 상태 코드와 함께 CRUD를 GET/POST/PUT/DELETE에 매핑합니다.
- 입력을 검증하고 잘못된 데이터에는
400을 반환합니다. IntoResponse를 구현하는 사용자 정의 오류 타입으로 일관된 JSON 오류를 제공합니다.
자주 묻는 질문
“엔드포인트와 모델” 강의는 무료인가요?
네 — “엔드포인트와 모델” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Learn Rust Coding 강의 전체를 잠금 해제할 수 있습니다. Learn Rust Coding 강의에는 총 4개의 강의가 포함되어 있습니다.
“엔드포인트와 모델”에서 뭘 배우나요?
경로와 데이터 브라우저에서 직접 실행하는 실습 코드로 Learn Rust Coding을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Learn Rust Coding을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Learn Rust Coding은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“엔드포인트와 모델” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Learn Rust Coding 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Learn Rust Coding 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.