Mongo-Style Users Feature
A full file-by-file example for a document-style users feature using MongoConfig and InMemoryMongoRepo.
This is the concrete Mongo-style feature workflow that was missing.
It shows:
- where each file goes
- what each file contains
- how the repo is registered
- how the service uses the repo
- how the controller stays normal
Project shape
Section titled “Project shape”src/ app_module.rs main.rs users/ controllers/ mod.rs users_controller.rs dto/ mod.rs create_user_dto.rs user_document.rs user_dto.rs services/ mod.rs users_service.rs mod.rsStep 1: create the DTO and document files
Section titled “Step 1: create the DTO and document files”Create src/users/dto/user_document.rs:
#[derive(Clone, serde::Serialize, serde::Deserialize)]pub struct UserDocument { pub id: String, pub name: String, pub email: String,}Create src/users/dto/create_user_dto.rs:
#[nestforge::dto]pub struct CreateUserDto { #[validate(required)] pub name: String, #[validate(required, email)] pub email: String,}Create src/users/dto/user_dto.rs:
#[nestforge::dto]pub struct UserDto { pub id: String, pub name: String, pub email: String,}Create src/users/dto/mod.rs:
pub mod create_user_dto;pub mod user_document;pub mod user_dto;
pub use create_user_dto::CreateUserDto;pub use user_document::UserDocument;pub use user_dto::UserDto;Step 2: create the service file
Section titled “Step 2: create the service file”Create src/users/services/users_service.rs:
use nestforge::InMemoryMongoRepo;use nestforge_data::DocumentRepo;
use crate::users::dto::{CreateUserDto, UserDocument, UserDto};
#[derive(Clone)]pub struct UsersService { repo: InMemoryMongoRepo<UserDocument>,}
impl UsersService { pub fn new(repo: InMemoryMongoRepo<UserDocument>) -> Self { Self { repo } }}
pub async fn list_users(service: &UsersService) -> anyhow::Result<Vec<UserDto>> { let docs = service.repo.find_all().await?;
Ok(docs .into_iter() .map(|doc| UserDto { id: doc.id, name: doc.name, email: doc.email, }) .collect())}
pub async fn create_user( service: &UsersService, dto: CreateUserDto,) -> anyhow::Result<UserDto> { let next = UserDocument { id: format!("user:{}", std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH)? .as_millis()), name: dto.name, email: dto.email, };
let stored = service.repo.insert(next).await?;
Ok(UserDto { id: stored.id, name: stored.name, email: stored.email, })}Create src/users/services/mod.rs:
pub mod users_service;
pub use users_service::{UsersService, create_user, list_users};Step 3: create the controller file
Section titled “Step 3: create the controller file”Create src/users/controllers/users_controller.rs:
use axum::Json;use nestforge::{controller, routes, ApiResult, Inject, List, ValidatedBody};
use crate::users::{ dto::{CreateUserDto, UserDto}, services::{UsersService, create_user, list_users},};
#[controller("/users")]pub struct UsersController;
#[routes]impl UsersController { #[nestforge::get("/")] #[nestforge::version("1")] async fn list(users: Inject<UsersService>) -> ApiResult<List<UserDto>> { let users = list_users(users.as_ref()) .await .map_err(|err| nestforge::HttpException::internal_server_error(err.to_string()))?; Ok(Json(users)) }
#[nestforge::post("/")] #[nestforge::version("1")] async fn create( users: Inject<UsersService>, body: ValidatedBody<CreateUserDto>, ) -> ApiResult<UserDto> { let user = create_user(users.as_ref(), body.value()) .await .map_err(|err| nestforge::HttpException::internal_server_error(err.to_string()))?; Ok(Json(user)) }}Create src/users/controllers/mod.rs:
pub mod users_controller;
pub use users_controller::UsersController;Step 4: wire the feature module
Section titled “Step 4: wire the feature module”Create src/users/mod.rs:
pub mod controllers;pub mod dto;pub mod services;
use nestforge::{ register_provider, Container, ControllerDefinition, InMemoryMongoRepo, MongoConfig, Provider,};
use self::{ controllers::UsersController, dto::UserDocument, services::UsersService,};
fn register_users_repo(container: &Container) -> anyhow::Result<()> { register_provider(container, Provider::value(InMemoryMongoRepo::<UserDocument>::new()))?; Ok(())}
fn register_users_service(container: &Container) -> anyhow::Result<()> { register_users_repo(container)?; register_provider( container, Provider::factory(|c| { let repo = c.resolve::<InMemoryMongoRepo<UserDocument>>()?; Ok(UsersService::new(repo)) }), )?; Ok(())}
fn register_mongo_config(container: &Container) -> anyhow::Result<()> { register_provider( container, Provider::value(MongoConfig::new("mongodb://localhost:27017", "my_app")), )?; Ok(())}
pub struct UsersModule;
impl nestforge::ModuleDefinition for UsersModule { fn register(container: &Container) -> anyhow::Result<()> { register_mongo_config(container)?; register_users_service(container) }
fn controllers() -> Vec<axum::Router<Container>> { vec![<UsersController as ControllerDefinition>::router()] }}Step 5: wire the root module
Section titled “Step 5: wire the root module”Create src/app_module.rs:
use nestforge::Container;
use crate::users::UsersModule;
pub struct AppModule;
impl nestforge::ModuleDefinition for AppModule { fn register(container: &Container) -> anyhow::Result<()> { UsersModule::register(container) }
fn controllers() -> Vec<axum::Router<Container>> { UsersModule::controllers() }}Step 6: bootstrap the app
Section titled “Step 6: bootstrap the app”Create src/main.rs:
mod app_module;mod users;
use app_module::AppModule;use nestforge::NestForgeFactory;
#[tokio::main]async fn main() -> anyhow::Result<()> { NestForgeFactory::<AppModule>::create()? .with_global_prefix("api") .listen(3000) .await}Step 7: verify the feature
Section titled “Step 7: verify the feature”After it runs, verify:
GET /api/v1/usersreturns an empty list initiallyPOST /api/v1/usersstores a new document throughInMemoryMongoRepoGET /api/v1/usersreturns the inserted document- the controller remains document-store agnostic
Why this example matters
Section titled “Why this example matters”This is the missing context the workflow page did not previously provide:
- exact
src/...file placement - actual provider registration
- actual controller shape
- actual module wiring