First Steps
Your first hands-on encounter with NestForge. Let's build a simple Users feature.
In this guide, you’ll learn the fundamental building blocks of NestForge by building a simple “Users” feature. We’ll cover modules, services, DTOs, and controllers.
-
Generate the Feature
NestForge provides a powerful CLI to speed up development. Instead of creating files manually, let’s use the generator.
Terminal window # Generate a modulenestforge g module users# Generate a service and controller within that modulenestforge g resource users --module usersThis creates a structured folder at
src/users/with everything we need. -
Define a Data Transfer Object (DTO)
Before we write business logic, we need to define the “shape” of the data entering and leaving our API. We’ll create a
UserDtofor output and aCreateUserDtofor input.src/users/dto/user_dto.rs #[nestforge::dto]pub struct UserDto {pub id: u64,pub name: String,pub email: String,}// src/users/dto/create_user_dto.rs#[nestforge::dto]pub struct CreateUserDto {#[validate(length(min = 3))]pub name: String,#[validate(email)]pub email: String,} -
Build the Service
Applications need a place for “business logic”. In NestForge, this happens in Services. For this example, we’ll use a built-in
ResourceServicewhich provides an in-memory storage for quick prototyping.src/users/services/users_service.rs use nestforge::ResourceService;use crate::users::dto::{UserDto, CreateUserDto};pub type UsersService = ResourceService<UserDto>;// A simple factory to seed our service with initial datapub fn users_service_factory() -> UsersService {ResourceService::with_seed(vec![UserDto { id: 1, name: "Alice".into(), email: "alice@example.com".into() }])} -
Expose the API via a Controller
Now we need to make our service reachable over HTTP. We’ll define a controller and inject our
UsersService.src/users/controllers/users_controller.rs use nestforge::{controller, routes, Inject, ApiResult, ValidatedBody};use axum::Json;use crate::users::dto::{UserDto, CreateUserDto};use crate::users::services::UsersService;#[controller("/users")]pub struct UsersController;#[routes]impl UsersController {#[nestforge::get("/")]async fn list(users: Inject<UsersService>) -> ApiResult<Vec<UserDto>> {// as_ref() gives us the service instance from the wrapperlet all = users.all().await;Ok(Json(all))}#[nestforge::post("/")]async fn create(users: Inject<UsersService>,body: ValidatedBody<CreateUserDto>) -> ApiResult<UserDto> {let newUser = users.create(body.value()).await?;Ok(Json(newUser))}} -
Wire it up in the Module
We need to tell NestForge about our new controller and service. We do this in the
UsersModule.src/users/mod.rs use nestforge::module;use self::controllers::UsersController;use self::services::users_service_factory;#[module(controllers = [UsersController],providers = [users_service_factory()],exports = [])]pub struct UsersModule; -
Import to the Root Module
Finally, import the
UsersModuleinto yourAppModuleso the framework knows it exists.src/app_module.rs #[module(imports = [UsersModule],controllers = [AppController],providers = [],)]pub struct AppModule; -
Test the Results
Start your application:
Terminal window cargo runTry hitting your new endpoint:
Terminal window curl http://localhost:3000/users
What did we learn?
Section titled “What did we learn?”- DTOs define the data contract.
- Services contain the logic.
- Controllers handle the HTTP transport.
- Modules act as the glue, wiring everything into the application’s dependency injection system.
Pro Tip: Automatic Validation
Section titled “Pro Tip: Automatic Validation”Notice the ValidatedBody<CreateUserDto> in our controller? Because we added #[validate] attributes to our DTO, NestForge automatically validates the incoming JSON. If a user sends an invalid email, they’ll get a 400 Bad Request with a helpful error message—without you writing a single if statement!