Course navigationPhase 13
Volume 04 · Phase 13

Professional Error Handling

Create a stable application error model that separates expected domain/transport failures from unexpected faults and keeps diagnostics observable.

14lessons + labs
6klesson words
9source references
What you will ship

Phase outcome and project increment

Learning outcome

Create a stable application error model that separates expected domain/transport failures from unexpected faults and keeps diagnostics observable.

Concrete deliverable

Error taxonomy + NestJS mapping layer/filters + structured error response with request IDs.

Figure 13.1 — Where this phase lives in TaskFlow
failure occurs
typed/application error
exception mapping
structured log
safe HTTP error
frontend recovery
Trace the flow left-to-right for implementation and right-to-left for debugging. At every arrow, ask what data crosses the boundary and what proves it is trustworthy.
Instructor mental model

Principles to keep while coding

01

Map known failures to stable HTTP error responses instead of leaking raw exceptions.

02

Keep unexpected errors observable to operators while returning safe messages to clients.

03

Use consistent error shapes so frontend code does not branch on dozens of unrelated formats.

04

Preserve enough context in logs to diagnose an error without sending secrets to the browser.

Code-along path

Guided phase lab

Do these in order. Each step should end with evidence before the next one starts.

  1. 01

    Inventory current TaskFlow failures and group them into validation, auth, forbidden, missing, conflict, infrastructure, and unexpected categories.

  2. 02

    Create explicit application errors for expected business outcomes rather than throwing arbitrary strings.

  3. 03

    Map errors to one stable HTTP envelope at a central boundary.

  4. 04

    Add request/correlation IDs to responses and logs.

  5. 05

    Trigger each error class intentionally and verify both client-safe response and server diagnostic context.

  6. 06

    Prove an unexpected exception returns a generic 500 while the real stack/context remains in logs.

Debugging studio

Failures you should know how to diagnose

Failure drill 1

Returning raw exception messages/stacks

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Failure drill 2

Catching everything inside each controller

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Failure drill 3

Using one generic 400 for semantically different failures

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Failure drill 4

Logging secrets/tokens with error context

Predict the symptom, capture evidence at the nearest boundary, change one variable, and verify the same reproduction after the fix.

Watch-order style

Lessons

The source chapters come first. Every phase now ends with a guided build, a debugging workshop, and an independent challenge so reading turns into transferable skill.

01

Error Taxonomy and API Contract

Error Taxonomy & API Contract Expected failures, unexpected faults, HTTP semantics, machine codes, and public error design Phase 13 - Professional Error Handling Volume 04 - Professional Bac

2 min
02

NestJS Exception Layer and Request Lifecycle

NestJS Exception Layer & Request Lifecycle How filters fit into NestJS 12 request processing and why global error boundaries beat repetitive try/catch Phase 13 - Professional Error Handling

2 min
03

Design the TaskFlow Error Envelope

Design the TaskFlow Error Envelope statusCode, code, message, details, requestId, timestamp, and path Phase 13 - Professional Error Handling Volume 04 - Professional Backend Architecture mil

2 min
04

Request IDs and Structured Logging

Request IDs & Structured Logging Use Fastify request IDs, response headers, timing, and NestJS 12 structured logger metadata Phase 13 - Professional Error Handling Volume 04 - Professional B

2 min
05

Global ApiExceptionFilter

Global ApiExceptionFilter Build a Fastify-compatible catch-all filter with HttpAdapterHost and safe normalization Phase 13 - Professional Error Handling Volume 04 - Professional Backend Arch

3 min
06

Prisma Error Translation

Prisma Error Translation Translate Prisma 7 P2002, P2003, and P2025 without leaking ORM internals Phase 13 - Professional Error Handling Volume 04 - Professional Backend Architecture milesto

2 min
07

Service vs Filter Boundaries

Service vs Filter Boundaries Domain-specific exceptions, contextual translation, and keeping business meaning out of global infrastructure code Phase 13 - Professional Error Handling Volume

2 min
08

Safe Production Error Exposure

Safe Production Error Exposure Sanitized client responses, protected diagnostics, logging hygiene, and privacy-aware failure handling Phase 13 - Professional Error Handling Volume 04 - Profe

2 min
09

Client Error Consumption and Debugging

Client Error Consumption & Debugging Parse the stable envelope in Next.js and distinguish API failures from transport failures Phase 13 - Professional Error Handling Volume 04 - Professional

2 min
10

Hands On Lab and Phase Exit

Hands-On Lab & Phase Exit Error matrix, correlation exercise, engineering challenges, oral exam, and completion checklist Phase 13 - Professional Error Handling Volume 04 - Professional Back

3 min
LAB

Guided Build Lab

Purpose Create a stable application error model that separates expected domain/transport failures from unexpected faults and keeps diagnostics observable.

3 min
LAB

Debugging Workshop

Goal Learn to diagnose this phase's failures systematically instead of reinstalling packages, changing random configuration, or copying a fix without understanding it.

2 min
LAB

Phase Synthesis & Independent Challenge

Mission Rebuild the capability of Phase 13 without following the exact code line-by-line. The goal is transfer: can you use the ideas when the variable names, UI, or route shape changes?

2 min
LAB

Mastery Capstone Lab

Integrate Phase 13 without following a transcript.

2 min
Practice ladder

Exercises that increase independence

Guided

Inventory current TaskFlow failures and group them into validation, auth, forbidden, missing, conflict, infrastructure, and unexpected categories.

Practice

Map errors to one stable HTTP envelope at a central boundary.

Debug

reproduce and diagnose — Returning raw exception messages/stacks.

Independent

deliver Error taxonomy + NestJS mapping layer/filters + structured error response with request IDs.

Explain-back

Can distinguish expected vs unexpected errors and Can map failures centrally.

Mastery gate

Do not leave the phase only because you reached the bottom

Phase exit criteria

Knowledge check

Scenario and concept quiz

These questions test engineering choices rather than whether you remember a lesson title.

0/0 correct · 0/8 answered
01Which practice best matches Phase 13's engineering goal?
02Which practice best matches Phase 13's engineering goal?
03Which practice best matches Phase 13's engineering goal?
04Which practice best matches Phase 13's engineering goal?
05You are working through the Phase 13 lab. Which next action is the most disciplined step?
06You are working through the Phase 13 lab. Which next action is the most disciplined step?
07You are working through the Phase 13 lab. Which next action is the most disciplined step?
08You are working through the Phase 13 lab. Which next action is the most disciplined step?
Source snapshot

Reference files

Attempt the work first. Use snapshots to compare a checkpoint, not as a substitute for understanding.

reference/TaskFlow_Error_Handling_Snapshot/README.txt
TaskFlow Phase 13 - Professional Error Handling

This snapshot contains the files changed by Phase 13. It assumes the Phase 12 project already exists.

Main additions:
- one consistent API error response shape
- a catch-all NestJS exception filter using HttpAdapterHost
- Prisma P2002/P2003/P2025 translation
- domain-specific not-found exceptions
- Fastify request IDs surfaced to clients and logs
- NestJS 12 structured logging metadata
- a small Next.js-side API error parser

Run apps/api, then execute tests/error-contract-smoke-tests.ps1.
reference/TaskFlow_Error_Handling_Snapshot/apps/api/src/app.module.ts
import { Module } from "@nestjs/common";
import { APP_FILTER, APP_INTERCEPTOR } from "@nestjs/core";
import { ApiExceptionFilter } from "./common/errors/api-exception.filter";
import { RequestLoggingInterceptor } from "./common/interceptors/request-logging.interceptor";
import { HealthModule } from "./health/health.module";
import { PrismaModule } from "./prisma/prisma.module";
import { TasksModule } from "./tasks/tasks.module";

@Module({
  imports: [
    HealthModule,
    PrismaModule,
    TasksModule,
  ],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: RequestLoggingInterceptor,
    },
    {
reference/TaskFlow_Error_Handling_Snapshot/apps/api/src/common/errors/api-error-response.ts
export type ApiErrorDetails = string[] | Record<string, unknown>;

export type ApiErrorResponse = {
  statusCode: number;
  code: string;
  message: string;
  requestId: string;
  timestamp: string;
  path: string;
  details?: ApiErrorDetails;
};
reference/TaskFlow_Error_Handling_Snapshot/apps/api/src/common/errors/api-exception.filter.ts
import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
  Logger,
} from "@nestjs/common";
import { HttpAdapterHost } from "@nestjs/core";
import { FastifyRequest } from "fastify";
import { Prisma } from "../../generated/prisma/client";
import { ApiErrorResponse } from "./api-error-response";

@Catch()
export class ApiExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(ApiExceptionFilter.name);

  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
reference/TaskFlow_Error_Handling_Snapshot/apps/api/src/common/errors/application.exceptions.ts
import {
  ConflictException,
  NotFoundException,
} from "@nestjs/common";

export class TaskNotFoundException extends NotFoundException {
  constructor(taskId: string) {
    super({
      code: "TASK_NOT_FOUND",
      message: `Task ${taskId} was not found.`,
    });
  }
}

export class ProjectNotFoundException extends NotFoundException {
  constructor(projectId: string) {
    super({
      code: "PROJECT_NOT_FOUND",
      message: `Project ${projectId} was not found.`,
    });
reference/TaskFlow_Error_Handling_Snapshot/apps/api/src/common/interceptors/request-logging.interceptor.ts
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  Logger,
  NestInterceptor,
} from "@nestjs/common";
import { FastifyReply, FastifyRequest } from "fastify";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";

@Injectable()
export class RequestLoggingInterceptor implements NestInterceptor {
  private readonly logger = new Logger(RequestLoggingInterceptor.name);

  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<unknown> {
    const http = context.switchToHttp();
reference/TaskFlow_Error_Handling_Snapshot/apps/api/src/tasks/tasks.service.ts
import { Injectable } from "@nestjs/common";
import { Prisma } from "../generated/prisma/client";
import { PrismaService } from "../prisma/prisma.service";
import {
  ProjectNotFoundException,
  TaskNotFoundException,
} from "../common/errors/application.exceptions";
import {
  CreateTaskDto,
  UpdateTaskDto,
} from "./tasks.schemas";

@Injectable()
export class TasksService {
  constructor(private readonly prisma: PrismaService) {}

  findAll() {
    return this.prisma.task.findMany({
      orderBy: { createdAt: "desc" },
    });
reference/TaskFlow_Error_Handling_Snapshot/apps/web/src/lib/api/api-error.ts
export type ApiErrorResponse = {
  statusCode: number;
  code: string;
  message: string;
  requestId: string;
  timestamp: string;
  path: string;
  details?: string[] | Record<string, unknown>;
};

export class ApiClientError extends Error {
  constructor(
    public readonly statusCode: number,
    public readonly code: string,
    message: string,
    public readonly requestId?: string,
    public readonly details?: ApiErrorResponse["details"],
  ) {
    super(message);
  }
reference/TaskFlow_Error_Handling_Snapshot/tests/error-contract-smoke-tests.ps1
# TaskFlow Phase 13 error-contract smoke tests
# Run while apps/api is running on http://localhost:4000

$base = "http://localhost:4000"

function Show-ErrorContract {
  param([scriptblock]$Request)

  try {
    & $Request
  }
  catch {
    $response = $_.Exception.Response

    if ($null -eq $response) {
      Write-Host "No HTTP response was returned."
      throw
    }

    $body = $_.ErrorDetails.Message | ConvertFrom-Json