Skip to content

Navigation Menu

Sign in
Sign up

웹 프레임워크

RAprogramm edited this page Jan 7, 2026 · 2 revisions

entity-derive를 인기 있는 Rust 웹 프레임워크와 함께 사용하는 방법입니다.

Axum

프로젝트 구조

src/
├── main.rs
├── entities/
│ ├── mod.rs
│ └── user.rs
├── handlers/
│ ├── mod.rs
│ └── users.rs
└── routes.rs

엔티티 정의

// src/entities/user.rs
use entity_derive::Entity;
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[derive(Entity, Clone)]
#[entity(table = "users", schema = "auth")]
pub struct User {
 #[id]
 pub id: Uuid,
 #[field(create, update, response)]
 pub username: String,
 #[field(create, update, response)]
 pub email: String,
 #[field(skip)]
 pub password_hash: String,
 #[auto]
 #[field(response)]
 pub created_at: DateTime<Utc>,
}

핸들러

// src/handlers/users.rs
use axum::{
 extract::{Path, State},
 http::StatusCode,
 Json,
};
use sqlx::PgPool;
use uuid::Uuid;
use crate::entities::user::*;
pub async fn create_user(
 State(pool): State<PgPool>,
 Json(payload): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), StatusCode> {
 let user = pool
 .create(payload)
 .await
 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
 Ok((StatusCode::CREATED, Json(UserResponse::from(&user))))
}
pub async fn get_user(
 State(pool): State<PgPool>,
 Path(id): Path<Uuid>,
) -> Result<Json<UserResponse>, StatusCode> {
 let user = pool
 .find_by_id(id)
 .await
 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
 .ok_or(StatusCode::NOT_FOUND)?;
 Ok(Json(UserResponse::from(&user)))
}
pub async fn update_user(
 State(pool): State<PgPool>,
 Path(id): Path<Uuid>,
 Json(payload): Json<UpdateUserRequest>,
) -> Result<Json<UserResponse>, StatusCode> {
 let user = pool
 .update(id, payload)
 .await
 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
 Ok(Json(UserResponse::from(&user)))
}
pub async fn delete_user(
 State(pool): State<PgPool>,
 Path(id): Path<Uuid>,
) -> Result<StatusCode, StatusCode> {
 let deleted = pool
 .delete(id)
 .await
 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
 if deleted {
 Ok(StatusCode::NO_CONTENT)
 } else {
 Err(StatusCode::NOT_FOUND)
 }
}
pub async fn list_users(
 State(pool): State<PgPool>,
) -> Result<Json<Vec<UserResponse>>, StatusCode> {
 let users = pool
 .list(100, 0)
 .await
 .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
 let responses: Vec<UserResponse> = users.iter().map(UserResponse::from).collect();
 Ok(Json(responses))
}

라우트

// src/routes.rs
use axum::{
 routing::{get, post, put, delete},
 Router,
};
use sqlx::PgPool;
use crate::handlers::users;
pub fn create_router(pool: PgPool) -> Router {
 Router::new()
 .route("/users", post(users::create_user))
 .route("/users", get(users::list_users))
 .route("/users/:id", get(users::get_user))
 .route("/users/:id", put(users::update_user))
 .route("/users/:id", delete(users::delete_user))
 .with_state(pool)
}

Main

// src/main.rs
use sqlx::postgres::PgPoolOptions;
use std::net::SocketAddr;
mod entities;
mod handlers;
mod routes;
#[tokio::main]
async fn main() {
 let database_url = std::env::var("DATABASE_URL")
 .expect("DATABASE_URL must be set");
 let pool = PgPoolOptions::new()
 .max_connections(5)
 .connect(&database_url)
 .await
 .expect("Failed to create pool");
 let app = routes::create_router(pool);
 let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
 println!("Listening on {}", addr);
 let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
 axum::serve(listener, app).await.unwrap();
}

Actix Web

핸들러

// src/handlers/users.rs
use actix_web::{web, HttpResponse, Responder};
use sqlx::PgPool;
use uuid::Uuid;
use crate::entities::user::*;
pub async fn create_user(
 pool: web::Data<PgPool>,
 payload: web::Json<CreateUserRequest>,
) -> impl Responder {
 match pool.create(payload.into_inner()).await {
 Ok(user) => HttpResponse::Created().json(UserResponse::from(&user)),
 Err(_) => HttpResponse::InternalServerError().finish(),
 }
}
pub async fn get_user(
 pool: web::Data<PgPool>,
 path: web::Path<Uuid>,
) -> impl Responder {
 let id = path.into_inner();
 match pool.find_by_id(id).await {
 Ok(Some(user)) => HttpResponse::Ok().json(UserResponse::from(&user)),
 Ok(None) => HttpResponse::NotFound().finish(),
 Err(_) => HttpResponse::InternalServerError().finish(),
 }
}
pub async fn update_user(
 pool: web::Data<PgPool>,
 path: web::Path<Uuid>,
 payload: web::Json<UpdateUserRequest>,
) -> impl Responder {
 let id = path.into_inner();
 match pool.update(id, payload.into_inner()).await {
 Ok(user) => HttpResponse::Ok().json(UserResponse::from(&user)),
 Err(_) => HttpResponse::InternalServerError().finish(),
 }
}
pub async fn delete_user(
 pool: web::Data<PgPool>,
 path: web::Path<Uuid>,
) -> impl Responder {
 let id = path.into_inner();
 match pool.delete(id).await {
 Ok(true) => HttpResponse::NoContent().finish(),
 Ok(false) => HttpResponse::NotFound().finish(),
 Err(_) => HttpResponse::InternalServerError().finish(),
 }
}
pub async fn list_users(pool: web::Data<PgPool>) -> impl Responder {
 match pool.list(100, 0).await {
 Ok(users) => {
 let responses: Vec<UserResponse> = users.iter().map(UserResponse::from).collect();
 HttpResponse::Ok().json(responses)
 }
 Err(_) => HttpResponse::InternalServerError().finish(),
 }
}

라우트

// src/routes.rs
use actix_web::web;
use crate::handlers::users;
pub fn configure(cfg: &mut web::ServiceConfig) {
 cfg.service(
 web::scope("/users")
 .route("", web::post().to(users::create_user))
 .route("", web::get().to(users::list_users))
 .route("/{id}", web::get().to(users::get_user))
 .route("/{id}", web::put().to(users::update_user))
 .route("/{id}", web::delete().to(users::delete_user)),
 );
}

Main

// src/main.rs
use actix_web::{App, HttpServer, web};
use sqlx::postgres::PgPoolOptions;
mod entities;
mod handlers;
mod routes;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
 let database_url = std::env::var("DATABASE_URL")
 .expect("DATABASE_URL must be set");
 let pool = PgPoolOptions::new()
 .max_connections(5)
 .connect(&database_url)
 .await
 .expect("Failed to create pool");
 HttpServer::new(move || {
 App::new()
 .app_data(web::Data::new(pool.clone()))
 .configure(routes::configure)
 })
 .bind(("127.0.0.1", 8080))?
 .run()
 .await
}

에러 처리

커스텀 에러 타입으로 더 나은 에러 처리:

use axum::{
 http::StatusCode,
 response::{IntoResponse, Response},
 Json,
};
use serde_json::json;
pub enum AppError {
 NotFound,
 Database(sqlx::Error),
 Validation(String),
}
impl IntoResponse for AppError {
 fn into_response(self) -> Response {
 let (status, message) = match self {
 AppError::NotFound => (StatusCode::NOT_FOUND, "리소스를 찾을 수 없습니다"),
 AppError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "데이터베이스 오류"),
 AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
 };
 (status, Json(json!({ "error": message }))).into_response()
 }
}
impl From<sqlx::Error> for AppError {
 fn from(err: sqlx::Error) -> Self {
 AppError::Database(err)
 }
}
// 핸들러에서 사용:
pub async fn get_user(
 State(pool): State<PgPool>,
 Path(id): Path<Uuid>,
) -> Result<Json<UserResponse>, AppError> {
 let user = pool
 .find_by_id(id)
 .await?
 .ok_or(AppError::NotFound)?;
 Ok(Json(UserResponse::from(&user)))
}

검증

validator 크레이트로 검증 추가:

use validator::Validate;
// 생성된 구조체에 Validate derive를 수동으로 추가하거나
// 리포지토리 메서드 호출 전에 검증
pub async fn create_user(
 State(pool): State<PgPool>,
 Json(payload): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), AppError> {
 // 수동 검증
 if payload.username.len() < 3 {
 return Err(AppError::Validation("사용자 이름이 너무 짧습니다".into()));
 }
 if !payload.email.contains('@') {
 return Err(AppError::Validation("잘못된 이메일".into()));
 }
 let user = pool.create(payload).await?;
 Ok((StatusCode::CREATED, Json(UserResponse::from(&user))))
}

페이지네이션

페이지네이션 헬퍼 예제:

use serde::Deserialize;
#[derive(Deserialize)]
pub struct Pagination {
 #[serde(default = "default_limit")]
 pub limit: i64,
 #[serde(default)]
 pub offset: i64,
}
fn default_limit() -> i64 {
 20
}
pub async fn list_users(
 State(pool): State<PgPool>,
 Query(pagination): Query<Pagination>,
) -> Result<Json<Vec<UserResponse>>, AppError> {
 let users = pool
 .list(pagination.limit.min(100), pagination.offset)
 .await?;
 let responses: Vec<UserResponse> = users.iter().map(UserResponse::from).collect();
 Ok(Json(responses))
}

🌐 Language

🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文


🇬🇧 English

Home

Getting Started

Features

Advanced


🇷🇺 Русский

Главная

Начало работы

Возможности

Продвинутое


🇰🇷 한국어

시작하기

기능

고급


🇪🇸 Español

Inicio

Comenzando

Características

Avanzado


🇨🇳 中文

首页

入门

功能

高级


📚 Reference

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /