Services
Handling business logic within NestForge.
Services are the backbone of NestForge applications. They contain the business logic and are injected into controllers or other services through dependency injection.
The #[injectable] Macro
Section titled “The #[injectable] Macro”The recommended way to define a service is with the #[injectable] macro. This marks the struct as a managed provider and automatically implements Clone.
Basic Usage
Section titled “Basic Usage”use nestforge::prelude::*;
#[injectable]pub struct UsersService;This automatically:
- Implements the
Injectabletrait - Registers the provider using
Self::default()internally - Implements
Clonefor the service
Custom Factory
Section titled “Custom Factory”For custom initialization, use the factory attribute:
#[injectable(factory = build_service)]pub struct CustomService;
fn build_service() -> CustomService { CustomService { /* custom setup */ }}With Dependencies
Section titled “With Dependencies”Services can depend on other services through the container:
use nestforge::prelude::*;
#[injectable]pub struct UsersService { store: InMemoryStore<User>,}
impl UsersService { pub fn new() -> Self { Self { store: InMemoryStore::default(), } }}Provider Types
Section titled “Provider Types”When you need more control over provider registration, you can manually define providers using the Provider enum:
Value Provider
Section titled “Value Provider”Provider::value(config)Factory Provider
Section titled “Factory Provider”Provider::factory(|container| { let store = container.resolve::<InMemoryStore<User>>()?; Ok(UsersService { store })})Request-Scoped Factory
Section titled “Request-Scoped Factory”Provider::request_factory(|container| { let request_id = container.resolve::<RequestId>()?; Ok(RequestScopedService { request_id })})Request-scoped factories resolve against a per-request child container, so they can depend on request data like RequestContext, RequestId, or authenticated identity.
Transient Factory
Section titled “Transient Factory”Provider::transient_factory(|container| { Ok(FreshService::new())})Transient factories build a fresh instance on every resolve and are useful for short-lived helper services.
Dependency Injection in Handlers
Section titled “Dependency Injection in Handlers”Use Inject<T> to resolve dependencies in handlers:
#[controller("/users")]pub struct UsersController;
impl UsersController { #[nestforge::get("/")] pub async fn list( &self, service: Inject<UsersService>, ) -> ApiResult<Vec<UserDto>> { let users = service.all(); Ok(Json(users)) }}ResourceService
Section titled “ResourceService”NestForge provides ResourceService<T> as a generic in-memory CRUD helper for quick prototyping:
use nestforge::prelude::*;
#[injectable(factory = users_service_seed)]pub struct UsersService { store: nestforge::ResourceService<UserDto>,}
pub fn users_service_seed() -> UsersService { UsersService { store: nestforge::ResourceService::with_seed(vec![ UserDto { id: 1, name: "Vernon".into(), email: "vernon@example.com".into() }, ]), }}Available Methods
Section titled “Available Methods”all()- Get all entitiesget(id)- Get by IDcount()- Count entitiesexists(id)- Check if existscreate(dto)- Create new entityupdate(id, dto)- Update entityreplace(id, dto)- Replace entitydelete(id)- Delete entity
Error Handling
Section titled “Error Handling”Pair ResourceService with helper methods:
.or_bad_request()?- Convert errors to 400.or_not_found_id("User", id)?- Convert missing to 404
let user = service .get(id) .or_not_found_id("User", id)?;DTO Requirements
Section titled “DTO Requirements”For services to work with DTOs:
- Entity DTO should be serializable/deserializable
- Entity DTO should implement
Identifiable - Create/update DTOs should be serializable
NestForge macros help reduce boilerplate:
#[nestforge::dto]pub struct UserDto { pub id: i64, pub name: String, pub email: String,}
nestforge::impl_identifiable!(UserDto, id);