Course outline

NestJS / Basic: modules and request contracts / Lesson 2

Controllers, routes and HTTP result contracts

Translate transport input into domain calls and return a deliberate public representation without bypassing the framework response lifecycle.

About 4 min readContent reviewed
On this page

Lesson goals

  • Combine controller prefixes and method routes correctly.
  • Distinguish ordinary return values from platform response handling.

A controller is an adapter, not the whole use case

A controller maps transport requests to application capabilities. Its route decorators identify paths and methods, while injected services should own reusable domain behavior. A class-level prefix and method-level path compose into the endpoint, so review global prefixes and versioning as well. A valid route parameter is still input, not evidence of object permission. Keep the controller narrow enough to review the sequence of validation, principal propagation, service call and public result. Moving a large transaction into a decorated method does not make it well isolated merely because Nest can invoke it.

Read a complete route contract from metadata

The fragment combines courses with :id to form a GET course lookup under any configured application prefix. It assumes CatalogService and decorator imports exist. The id parameter is received as a string; a TypeScript annotation does not parse a runtime value. The service here represents a public-read policy, not arbitrary database access. Framework-managed return values let Nest serialize the selected result. No controller, container or HTTP adapter was executed for this example, so its expected mapping is a reasoned contract rather than a tested live route.

typescript Illustrative course controller
@Controller("courses")
export class CoursesController {
  constructor(private readonly catalog: CatalogService) {}
  @Get(":id")
  find(@Param("id") id: string) {
    return this.catalog.publicCard(id);
  }
}

Without an extra global prefix, the route is GET /courses/:id. The parameter remains text until explicit validation/transformation.

Choose one response owner

Nest’s standard response mode handles returned objects and promises. Injecting a native @Res response can switch responsibility toward platform-specific handling, where the handler must finish the response unless configured for passthrough. Mixing a native send with an ordinary returned object can create confusion or duplicate work. Status defaults differ by method, and explicit @HttpCode or exceptions should reflect the intended outcome. A 204 must not be designed with a content body. Keep unexpected source failures separate from expected absence rather than returning every failure as a successful object with an error-looking field.

Map a domain outcome before transport

The local function turns a synthetic visible record or absence into a response plan. It keeps public fields narrow and uses a documented not-found code. It does not instantiate HttpException or test how Nest serializes one. A domain might intentionally use the same public absence result for denied objects to avoid disclosing existence; preserve that policy consistently. Test the exact selected ID and title, not just a success status, because a correct-looking response can still belong to another tenant. Transport mapping should not silently alter the domain’s disclosure rule.

javascript Complete offline result mapper
function resultPlan(record) {
  return record ? {status:200,body:{id:record.id,title:record.title}}
    : {status:404,body:{code:"COURSE_NOT_FOUND"}};
}
console.assert(resultPlan({id:"c1",title:"Routes"}).body.id === "c1");
console.assert(resultPlan(null).status === 404);

The visible record maps to the selected public card; absence maps to the declared 404 plan. No HTTP response is sent.

Verify path, method and effect separately

An unmatched path is different from a matched route whose source returns no object. Review fixed paths before broad parameter paths and retain fixtures for global prefix and version combinations. GET should preserve the application’s safe-read contract, while writes need explicit validation, authorization and replay semantics. A route decorator does not implement any of those policies. Test a controller with an injected fake for argument forwarding, then a composed application for route matching and response handling. This course runs only the result mapper, leaving actual Nest routing and transport behavior unexecuted.

Common errors and fixes

A numeric annotation leaves a string at runtime

Why it happens
Type information was mistaken for runtime parsing.
How to fix it
Use an explicit pipe or validated domain conversion.
Prevent it next time
Test the actual runtime input shape.

A native response path hangs

Why it happens
Response ownership changed but no final send occurred.
How to fix it
Choose standard return mode or finish the native response deliberately.
Prevent it next time
Review every response branch and passthrough assumption.

A source outage returns a successful empty card

Why it happens
Failure and legitimate absence were collapsed.
How to fix it
Preserve distinct domain/error categories under safe disclosure.
Prevent it next time
Test absent and failing sources separately.

Check your understanding

1. What does the local result mapper not establish about Nest?

Show worked solution

It does not establish decorator registration, route matching, parameter extraction, exception filters or actual status serialization. It proves a plain function’s decision for two fixtures. Keep those claims separate and use a compiled application test for the actual controller/adapter boundary when authorized.

2. Why avoid returning a full entity from the controller?

Show worked solution

Persistence fields can include internal state that is irrelevant or unsafe for callers. A reviewed public projection makes allowed keys stable and independently testable. Apply eligibility before projection, and review serialization behavior if class-based exclusions are used; an output mechanism is not a substitute for object authorization.

Key takeaways

  • Controllers adapt routes; services own reusable use cases.
  • Runtime input and response ownership require explicit decisions.

Official references