Mongo Workflow
A file-by-file workflow for building a document-style users feature with MongoConfig and InMemoryMongoRepo.
This page is the actual Mongo workflow.
It shows:
- where files go
- what code goes in each file
- how the repo is registered
- how the service uses it
- how the controller stays clean
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
Section titled “Step 2: create the service”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
Section titled “Step 3: create the controller”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,};
pub struct UsersModule;
impl nestforge::ModuleDefinition for UsersModule { fn register(container: &Container) -> anyhow::Result<()> { register_provider( container, Provider::value(MongoConfig::new("mongodb://localhost:27017", "my_app")), )?; register_provider( container, Provider::value(InMemoryMongoRepo::<UserDocument>::new()), )?; register_provider( container, Provider::factory(|c| { let repo = c.resolve::<InMemoryMongoRepo<UserDocument>>()?; Ok(UsersService::new(repo)) }), )?; Ok(()) }
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”Verify this sequence:
GET /api/v1/usersreturns an empty list initiallyPOST /api/v1/usersinserts throughInMemoryMongoRepoGET /api/v1/usersreturns the inserted document- the controller never depends directly on repo APIs
Current support boundary
Section titled “Current support boundary”This is the current supported Mongo-style path in the workspace:
MongoConfigis availableInMemoryMongoRepo<T>is the concrete adapter shown in the framework- there is not yet a checked-in full external Mongo driver example app in this repo
What to read next
Section titled “What to read next”For the backend overview, see Data Layer.