Controllers
Handling incoming requests and returning responses in NestForge.
Controllers are responsible for handling incoming requests and returning responses to the client.
A controller’s purpose is to receive specific requests for the application. The routing mechanism controls which controller receives which requests. Frequently, each controller has more than one route, and different routes can perform different actions.
Routing
Section titled “Routing”In order to create a basic controller, we use the #[controller] macro. We’ll specify an optional path prefix in the #[controller()] macro to easily group a set of related routes and minimize repetitive code.
use nestforge::{controller, routes, ApiResult};use axum::Json;
#[controller("/users")]pub struct UsersController;
#[routes]impl UsersController { #[nestforge::get("/")] async fn find_all() -> ApiResult<Vec<String>> { Ok(Json(vec!["Alice".to_string(), "Bob".to_string()])) }}Route Versioning
Section titled “Route Versioning”NestForge also supports route versioning through the
#[nestforge::version("...")] attribute.
#[controller("/versioning")]pub struct VersioningController;
#[routes]impl VersioningController { #[nestforge::get("/hello")] #[nestforge::version("1")] async fn hello_v1() -> String { "Hello from API v1".to_string() }
#[nestforge::get("/hello")] #[nestforge::version("2")] async fn hello_v2() -> String { "Hello from API v2".to_string() }}When the application bootstrap uses .with_version("v1"), the version segment
becomes part of the mounted route structure.
Request Object
Section titled “Request Object”Handlers often need access to the client request details. NestForge provides a set of extractors that you can use in your handler’s signature.
| Extractor | Description |
|---|---|
Param<T> | Route parameters (e.g., :id) |
Query<T> | Query string parameters |
Body<T> | The request body (JSON) |
Headers | Request headers |
Cookies | Request cookies |
Inject<T> | Resolve a provider from the DI container |
ValidatedBody<T> | JSON body with automatic validation |
Example: Path Parameters
Section titled “Example: Path Parameters”#[nestforge::get("/{id}")]async fn find_one(id: Param<u64>) -> ApiResult<UserDto> { let user_id = id.value(); // ...}Example: Query Strings
Section titled “Example: Query Strings”#[derive(Deserialize)]struct PaginationQuery { page: Option<usize>, limit: Option<usize>,}
#[nestforge::get("/")]async fn find_all(query: Query<PaginationQuery>) -> ApiResult<Vec<UserDto>> { let page = query.page.unwrap_or(1); // ...}Request Payloads (DTOs)
Section titled “Request Payloads (DTOs)”Our previous example of the POST route handler didn’t accept any client params. Let’s fix this by adding the Body extractor here.
But first, we need to determine the DTO (Data Transfer Object) schema. A DTO is an object that defines how the data will be sent over the network.
#[nestforge::dto]pub struct CreateUserDto { pub name: String, pub age: i32,}
#[nestforge::post("/")]async fn create(body: Body<CreateUserDto>) -> ApiResult<UserDto> { let dto = body.value(); // ...}Validation
Section titled “Validation”NestForge provides a ValidatedBody<T> extractor that automatically validates the incoming request body against your DTO rules.
#[nestforge::dto]pub struct CreateUserDto { #[validate(length(min = 3))] pub name: String, #[validate(range(min = 18, max = 100))] pub age: i32,}
#[nestforge::post("/")]async fn create(body: ValidatedBody<CreateUserDto>) -> ApiResult<UserDto> { // If validation fails, NestForge returns a 400 Bad Request automatically. let dto = body.value(); // ...}Status Codes
Section titled “Status Codes”By default, the response status code is always 200 OK, except for POST requests which are 201 Created. We can easily change this behavior by returning a custom result or using the HttpException type.
#[nestforge::post("/")]async fn create() -> ApiResult<UserDto> { if something_failed { return Err(HttpException::bad_request("Invalid data").into()); } // ...}Response Envelopes
Section titled “Response Envelopes”To maintain a consistent API structure, you might want to wrap all your responses in a standard “envelope”.
#[nestforge::get("/")]async fn find_all() -> ApiResult<Vec<UserDto>> { let data = vec![...]; Ok(ResponseEnvelope::paginated(data, total, page, limit).into())}This would produce a JSON response like:
{ "data": [...], "meta": { "total": 100, "page": 1, "limit": 10 }}