Skip to main content

Components

This level opens the API container and shows how a request travels through it. The shape is the same for every one of the API's fifty-eight routes, which is the point: there is exactly one path in, and the authorization decisions happen before any handler code runs.

The five layers​

The edge. Two things touch every request before any Nest code does. cookieParser turns the session cookie into a value the guards can read, and the ValidationPipe enforces the DTO a route declares — rejecting unknown fields rather than ignoring them, and stopping at the first failure per field so the error reads as one sentence rather than three overlapping complaints.

The guards. Authentication and authorization run here, globally, before any handler. JwtAuthGuard verifies the signed session token and re-reads the account row on every request. That extra read is deliberate: it is what lets an administrator suspend an account and have it take effect on that account's very next request, which a stateless token cannot do on its own. RolesGuard then checks the roles the route declared.

The direction of the default matters. Both guards are applied to the whole application, and a route opts out with @Public(). A new controller is therefore protected unless its author says otherwise, rather than open until somebody remembers to protect it.

The controllers. Eleven of them, one per HTTP surface, grouped into twelve modules. A controller's job is to declare a route, name the DTO that describes its body, and state who may call it. It holds no business rules.

The services. Where the rules live — whether this patient owns this appointment, whether this doctor took part in this consultation, whether this slot is actually offered. Ownership checks sit next to the query that needs them, so a caller cannot reach a record by knowing its identifier.

Data access. PrismaService is the single gateway to PostgreSQL. It is also what openapi:emit has to satisfy: instantiating the application opens this connection, which is why generating the API description needs a database available.

The two branches off the main path​

The model runtime. Only the records module reaches it, through an LlmProvider abstraction with two implementations — one that talks to Ollama and one that is simply disabled. Which one is bound is decided by configuration at startup, so the rest of the application never has to ask whether generation is available; it asks the provider and gets an answer.

The exception filter. Every failure — a thrown HttpException, a validation error, an unexpected crash — leaves through one filter and arrives as the same JSON shape. A client never has to distinguish "the API said no" from "the API broke" by inspecting the response's structure.

Why this shape​

A route that needs authorization metadata cannot forget to add it, because the metadata is read from decorators the guards already consult. The API reference is generated by walking these same controllers, so the document describes the routes that exist rather than the routes someone meant to write.