-
-
Notifications
You must be signed in to change notification settings - Fork 0
웹 프레임워크
RAprogramm edited this page Jan 7, 2026
·
2 revisions
entity-derive를 인기 있는 Rust 웹 프레임워크와 함께 사용하는 방법입니다.
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) }
// 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(); }
// 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)), ); }
// 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)) }
🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文
Getting Started
Features
Advanced
Начало работы
Возможности
Продвинутое
시작하기
기능
고급
Comenzando
Características
Avanzado
入门
功能
高级