Skip to content

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 recommended way to define a service is with the #[injectable] macro. This marks the struct as a managed provider and automatically implements Clone.

use nestforge::prelude::*;
#[injectable]
pub struct UsersService;

This automatically:

  • Implements the Injectable trait
  • Registers the provider using Self::default() internally
  • Implements Clone for the service

For custom initialization, use the factory attribute:

#[injectable(factory = build_service)]
pub struct CustomService;
fn build_service() -> CustomService {
CustomService { /* custom setup */ }
}

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

When you need more control over provider registration, you can manually define providers using the Provider enum:

Provider::value(config)
Provider::factory(|container| {
let store = container.resolve::<InMemoryStore<User>>()?;
Ok(UsersService { store })
})
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.

Provider::transient_factory(|container| {
Ok(FreshService::new())
})

Transient factories build a fresh instance on every resolve and are useful for short-lived helper services.

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

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() },
]),
}
}
  • all() - Get all entities
  • get(id) - Get by ID
  • count() - Count entities
  • exists(id) - Check if exists
  • create(dto) - Create new entity
  • update(id, dto) - Update entity
  • replace(id, dto) - Replace entity
  • delete(id) - Delete entity

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)?;

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