Actix Web 소개
Actix Web은 Rust의 대표적인 웹 프레임워크로, TechEmpower 벤치마크에서 상위권을 유지하는 고성능 프레임워크입니다. Rust의 소유권 시스템과 타입 안전성 덕분에 메모리 안전한 서버를 구축할 수 있으며, async/await 기반의 비동기 처리를 지원합니다.
프로젝트 설정
# Cargo.toml
[package]
name = "api-server"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4"
actix-rt = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
env_logger = "0.10"
dotenv = "0.15"
기본 서버와 라우팅
use actix_web::{web, App, HttpServer, HttpResponse, middleware};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct User {
id: uuid::Uuid,
name: String,
email: String,
created_at: chrono::NaiveDateTime,
}
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Deserialize)]
struct QueryParams {
page: Option<u32>,
limit: Option<u32>,
}
async fn get_users(
pool: web::Data<sqlx::PgPool>,
query: web::Query<QueryParams>,
) -> HttpResponse {
let limit = query.limit.unwrap_or(20).min(100);
let offset = (query.page.unwrap_or(1) - 1) * limit;
let users = sqlx::query_as!(
User,
"SELECT id, name, email, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2",
limit as i64,
offset as i64,
)
.fetch_all(pool.get_ref())
.await
.unwrap();
HttpResponse::Ok().json(users)
}
async fn create_user(
pool: web::Data<sqlx::PgPool>,
body: web::Json<CreateUser>,
) -> HttpResponse {
let user = sqlx::query_as!(
User,
"INSERT INTO users (id, name, email, created_at) VALUES ($1, $2, $3, NOW()) RETURNING *",
uuid::Uuid::new_v4(),
body.name,
body.email,
)
.fetch_one(pool.get_ref())
.await
.unwrap();
HttpResponse::Created().json(user)
}
에러 처리와 미들웨어
use actix_web::error::ResponseError;
use std::fmt;
#[derive(Debug)]
enum ApiError {
NotFound(String),
BadRequest(String),
Internal(String),
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ApiError::NotFound(msg) => write!(f, "Not Found: {}", msg),
ApiError::BadRequest(msg) => write!(f, "Bad Request: {}", msg),
ApiError::Internal(msg) => write!(f, "Internal Error: {}", msg),
}
}
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
match self {
ApiError::NotFound(msg) =>
HttpResponse::NotFound().json(serde_json::json!({"error": msg})),
ApiError::BadRequest(msg) =>
HttpResponse::BadRequest().json(serde_json::json!({"error": msg})),
ApiError::Internal(msg) =>
HttpResponse::InternalServerError().json(serde_json::json!({"error": msg})),
}
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
env_logger::init();
let pool = sqlx::PgPool::connect(&std::env::var("DATABASE_URL").unwrap())
.await.unwrap();
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(pool.clone()))
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
.service(
web::scope("/api/v1")
.route("/users", web::get().to(get_users))
.route("/users", web::post().to(create_user))
)
})
.bind("0.0.0.0:8080")?
.workers(num_cpus::get())
.run()
.await
}
- Actix Web의 Extractor 패턴으로 Path, Query, Json, Data 등을 타입 안전하게 추출합니다
sqlx::query_as!매크로는 컴파일 타임에 SQL 쿼리를 검증합니다middleware::Compress로 자동 gzip/brotli 압축을 적용합니다- Rust의 zero-cost abstractions 덕분에 Go, Java 대비 메모리 사용량이 매우 적습니다
- 프로덕션에서는
shuttle.rs또는 Docker로 쉽게 배포할 수 있습니다
댓글 0