InteractiveFrameworks

The skeleton

Start the shelter service from almost nothing: settings from .env, a health check, and the cats API with every rule a client can break checked at the door: validated bodies, refused unknown properties, numeric ids, and 404s that say what was missing.

What you'll learn

  • Read a specification and turn it into modules, controllers, services and DTOs without a step-by-step guide
  • Load settings from .env with ConfigModule and read them with ConfigService
  • Validate every body globally, refuse properties a DTO does not declare, and derive an update DTO from a create DTO
  • Answer the API's errors as Nest does: 400 with the rules that failed, 404 naming what was not found, 204 for a deletion

Every course so far built one feature at a time on a small cats API, and each lesson kept to its topic: the database lessons had no accounts, the security lessons had no database. A real service has all of it at once, and the hard part is not any single feature but making them fit. This course builds one service, the Whiskers Shelter API, across six lessons, and each lesson keeps everything the previous ones built working. The lessons also change shape: you get a specification and the files it needs, not a walkthrough. The theory explains the decisions; the code is yours.

The service

By the end of the course the API looks like this:

RouteWhoLesson
GET /healthanyone1
GET /cats, GET /cats/:idanyone1
POST /cats, PATCH /cats/:id, DELETE /cats/:idstaff1, staff-only from 3
POST /cats/:id/adopta signed-in adopter2, by account from 3
GET /adoptions, GET /adoptions/minestaff, the adopter2, 3
POST /auth/register, POST /auth/login, GET /auth/meanyone, anyone, signed in3
GET /api, GET /api-jsonanyone5
socket event cat:adoptedevery connected screen5

Lesson 4 adds no route: it hardens all of them. Lesson 6 adds no code: you write the tests that prove the rest.

Settings belong in configuration

The shelter's name, and from lesson 3 a signing secret and the cost of hashing passwords, differ between a laptop and production. ConfigModule.forRoot() reads .env into ConfigService, and isGlobal: true makes it injectable everywhere without importing the module again. Read a value with config.getOrThrow('SHELTER_NAME'): a missing setting should stop the service at the first request that needs it, not quietly become undefined.

Validation at the door

One ValidationPipe in main.ts validates every body against its DTO. Three options turn it from a checker into a gate:

app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));

whitelist strips properties the DTO does not declare; forbidNonWhitelisted refuses them instead, so a client that sends owner learns it is not part of the API rather than wondering why it was ignored. transform hands the handler an instance of the DTO class and converts route parameters to their declared types.

An update accepts any subset of a creation's fields under the same rules. Writing that class twice drifts the day one rule changes. PartialType(CreateCatDto) from @nestjs/mapped-types derives it, rules included, every property optional.

Errors that say what happened

A client reads errors more often than successes. ParseIntPipe on :id answers abc with a 400 before your code runs. A cat that does not exist is a NotFoundException with a message naming it, Cat #9 not found, which Nest turns into a 404 with that message. A deletion answers 204 with no body, set with @HttpCode(204), because there is nothing left to describe.

Your task

The starter boots and lists two cats; the rest is yours. The graded requests are the specification, and each description says what the request proves.

  1. Settings from .env for the whole app, and GET /health answering { status: 'ok', shelter } with the name from it.
  2. Every body validated, unknown properties refused, parameters converted.
  3. A new cat has a name of 2 to 30 characters, a whole-number age from 0 to 30 and an optional breed; an update carries any of those under the same rules.
  4. The cats routes: the list, filtered by ?breed=; one cat by numeric id, 404 Cat #9 not found when missing; create (ids go on from 3, a missing breed is null); update; remove with 204.

When it fails

  • Nest can't resolve dependencies of the AppController (?): ConfigService is not available; ConfigModule.forRoot() is missing from the imports.
  • property age should not exist on a valid PATCH: UpdateCatDto declares no properties, so the whitelist refuses all of them. Derive it with PartialType.
  • Cat #NaN not found for /cats/abc, a 404 where a 400 belongs: transform converted the id to a number without a pipe to refuse it. Add ParseIntPipe.
  • owner is accepted, or dropped without a word: forbidNonWhitelisted is not set.

Remember

  • Settings live in .env, read through ConfigService, with getOrThrow for the ones the service cannot run without.
  • whitelist, forbidNonWhitelisted and transform make the ValidationPipe a gate, not a filter.
  • Derive update DTOs with PartialType so the rules live once.
  • An error names what was wrong: 400 with the rules, 404 with the id, 204 for nothing to say.
Stuck? Show a hint

main.ts: app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })). app.module.ts: ConfigModule.forRoot({ isGlobal: true }) and AppController. GET /health reads config.getOrThrow('SHELTER_NAME'). CreateCatDto: @IsString() @Length(2, 30), @IsInt() @Min(0) @Max(30), @IsOptional() @IsString(). UpdateCatDto extends PartialType(CreateCatDto) from @nestjs/mapped-types. ParseIntPipe on :id, @HttpCode(204) on delete.