Skip to content

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
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.rs

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;

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};

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;

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()]
}
}

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()
}
}

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
}

After it runs, verify:

  1. GET /api/v1/users returns an empty list initially
  2. POST /api/v1/users stores a new document through InMemoryMongoRepo
  3. GET /api/v1/users returns the inserted document
  4. the controller remains document-store agnostic

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