Step 03: Actix-web으로 REST API 만들기
0 studying now
core 90 min
Actix-web으로 REST API 만들기
웹 서버를 시작하고 기본 라우트를 만듭니다.
Execute this step
Run from project root:
cargo run --bin webStep 3: Actix-web으로 REST API 만들기
학습 목표
- Actix-web 서버 시작하기
- HTTP 라우트와 핸들러 만들기
- JSON으로 요청/응답 주고받기
REST API 설계
| 메서드 | 경로 | 설명 | |--------|------|------| | GET | /books | 전체 도서 목록 | | GET | /books/{id} | 특정 도서 조회 | | POST | /books | 새 도서 추가 | | POST | /books/{id}/borrow | 도서 대여 | | POST | /books/{id}/return | 도서 반납 |
웹 서버 코드
src/bin/web.rs 생성:
1use actix_web::{web, App, HttpServer, HttpResponse, Result}; 2use book_manager::{AsyncLibrary, Category, Book}; 3use serde::{Deserialize, Serialize}; 4use std::sync::Arc; 5 6// 공유 상태 7struct AppState { 8 library: Arc<AsyncLibrary>, 9} 10 11// JSON 요청 타입 12#[derive(Deserialize)] 13struct CreateBookRequest { 14 title: String, 15 author: String, 16 category: String, 17} 18 19#[derive(Deserialize)] 20struct BorrowRequest { 21 borrower: String, 22 due_date: String, 23} 24 25// JSON 응답 타입 26#[derive(Serialize)] 27struct ApiResponse<T> { 28 success: bool, 29 data: Option<T>, 30 message: Option<String>, 31} 32 33impl<T> ApiResponse<T> { 34 fn success(data: T) -> Self { 35 ApiResponse { 36 success: true, 37 data: Some(data), 38 message: None, 39 } 40 } 41 42 fn error(message: String) -> Self { 43 ApiResponse { 44 success: false, 45 data: None, 46 message: Some(message), 47 } 48 } 49} 50 51// 핸들러 함수들 52async fn health_check() -> HttpResponse { 53 HttpResponse::Ok().json(ApiResponse::success("OK")) 54} 55 56async fn list_books(data: web::Data<AppState>) -> Result<HttpResponse> { 57 let books = data.library.list_all().await; 58 Ok(HttpResponse::Ok().json(ApiResponse::success(books))) 59} 60 61async fn get_book( 62 data: web::Data<AppState>, 63 path: web::Path<u32>, 64) -> Result<HttpResponse> { 65 let id = path.into_inner(); 66 67 match data.library.find_book(id).await { 68 Some(book) => Ok(HttpResponse::Ok().json(ApiResponse::success(book))), 69 None => Ok(HttpResponse::NotFound().json( 70 ApiResponse::<()>::error(format!("책 ID {}를 찾을 수 없습니다", id)) 71 )), 72 } 73} 74 75async fn create_book( 76 data: web::Data<AppState>, 77 req: web::Json<CreateBookRequest>, 78) -> Result<HttpResponse> { 79 let category: Category = req.category.as_str().into(); 80 81 let id = data.library.add_book( 82 req.title.clone(), 83 req.author.clone(), 84 category, 85 ).await; 86 87 Ok(HttpResponse::Created().json(ApiResponse::success(id))) 88} 89 90async fn borrow_book( 91 data: web::Data<AppState>, 92 path: web::Path<u32>, 93 req: web::Json<BorrowRequest>, 94) -> Result<HttpResponse> { 95 let id = path.into_inner(); 96 97 match data.library.borrow_book( 98 id, 99 req.borrower.clone(), 100 req.due_date.clone(), 101 ).await { 102 Ok(_) => Ok(HttpResponse::Ok().json(ApiResponse::success("대여 완료"))), 103 Err(e) => Ok(HttpResponse::BadRequest().json( 104 ApiResponse::<()>::error(e) 105 )), 106 } 107} 108 109async fn return_book( 110 data: web::Data<AppState>, 111 path: web::Path<u32>, 112) -> Result<HttpResponse> { 113 let id = path.into_inner(); 114 115 match data.library.return_book(id).await { 116 Ok(_) => Ok(HttpResponse::Ok().json(ApiResponse::success("반납 완료"))), 117 Err(e) => Ok(HttpResponse::BadRequest().json( 118 ApiResponse::<()>::error(e) 119 )), 120 } 121} 122 123#[actix_web::main] 124async fn main() -> std::io::Result<()> { 125 println!("🚀 웹 서버 시작: http://localhost:8080"); 126 127 // 공유 상태 생성 128 let library = Arc::new(AsyncLibrary::new()); 129 130 // 샘플 데이터 추가 131 library.add_book( 132 "러스트 프로그래밍".to_string(), 133 "스티브".to_string(), 134 Category::Science, 135 ).await; 136 library.add_book( 137 "해리포터".to_string(), 138 "J.K. 롤링".to_string(), 139 Category::Fiction, 140 ).await; 141 142 let app_state = web::Data::new(AppState { library }); 143 144 HttpServer::new(move || { 145 App::new() 146 .app_data(app_state.clone()) 147 .route("/health", web::get().to(health_check)) 148 .route("/books", web::get().to(list_books)) 149 .route("/books/{id}", web::get().to(get_book)) 150 .route("/books", web::post().to(create_book)) 151 .route("/books/{id}/borrow", web::post().to(borrow_book)) 152 .route("/books/{id}/return", web::post().to(return_book)) 153 }) 154 .bind(("127.0.0.1", 8080))? 155 .run() 156 .await 157}
API 테스트
1. 서버 실행
cargo run --bin web
2. curl로 테스트
헬스 체크:
curl http://localhost:8080/health
전체 도서 목록:
curl http://localhost:8080/books
특정 도서 조회:
curl http://localhost:8080/books/1
새 도서 추가:
1curl -X POST http://localhost:8080/books \ 2 -H "Content-Type: application/json" \ 3 -d '{ 4 "title": "클린 코드", 5 "author": "로버트 마틴", 6 "category": "nonfiction" 7 }'
도서 대여:
1curl -X POST http://localhost:8080/books/1/borrow \ 2 -H "Content-Type: application/json" \ 3 -d '{ 4 "borrower": "김철수", 5 "due_date": "2026-02-20" 6 }'
도서 반납:
curl -X POST http://localhost:8080/books/1/return
Cargo.toml 수정
1[[bin]] 2name = "cli" 3path = "src/main.rs" 4 5[[bin]] 6name = "web" 7path = "src/bin/web.rs"
체크리스트
- [ ] Actix-web 서버를 시작했습니다
- [ ] REST API 라우트를 정의했습니다
- [ ] JSON 요청을 받아 처리했습니다
- [ ] JSON 응답을 반환했습니다
- [ ] curl로 API를 테스트했습니다
다음 단계
Step 4에서는 에러 처리를 개선하고 미들웨어를 추가합니다. 축하합니다! 이제 Rust로 웹 서버를 만들 수 있습니다!