Building Robust APIs with DTOs, Validation, and Prisma Enums in NestJS

Working on the Court project, we're continuously enhancing its data handling capabilities. A recent focus involved strengthening our API's data integrity and domain modeling by integrating DTOs, comprehensive validation, and domain-specific enums, all backed by a robust Prisma schema update. This ensures that data entering our system is always consistent and adheres to our business rules.

The Challenge: Data Integrity and Domain Consistency

Building scalable and maintainable applications hinges on predictable data. Without clear contracts for incoming data, APIs can become fragile, leading to runtime errors, inconsistent database states, and a higher debugging overhead. Allowing arbitrary data structures or loosely typed values to flow through the application without validation opens the door to numerous issues, from security vulnerabilities to simple data entry errors. Furthermore, representing fixed sets of values (like statuses or types) without proper enumeration can lead to 'magic strings' and decreased readability.

A Structured Approach: DTOs, Validation, and Enums

To combat these challenges, we've adopted a multi-pronged approach that leverages Data Transfer Objects (DTOs), robust validation, and domain-driven enums, all synchronized with our PostgreSQL database via Prisma.

Data Transfer Objects (DTOs)

DTOs act as boundary objects, defining the precise shape of data expected from a client request or sent as a response. In a NestJS application, DTOs are typically classes, which, combined with class-validator and class-transformer, provide a powerful way to define and validate data structures.

import { IsString, IsEnum, MinLength, IsNotEmpty } from 'class-validator';

enum CaseStatus {
  PENDING = 'PENDING',
  ACTIVE = 'ACTIVE',
  CLOSED = 'CLOSED',
  ARCHIVED = 'ARCHIVED'
}

export class CreateCourtCaseDto {
  @IsString()
  @IsNotEmpty()
  @MinLength(3)
  title: string;

  @IsString()
  @IsNotEmpty()
  description: string;

  @IsEnum(CaseStatus)
  status: CaseStatus;
}

This CreateCourtCaseDto clearly defines what a new court case should look like, enforcing string types, minimum lengths, and requiring the status to be one of the predefined CaseStatus values.

Validation with class-validator

The decorators like @IsString(), @MinLength(), and @IsEnum() from class-validator automatically apply validation rules to the incoming request body. NestJS pipes (e.g., ValidationPipe) intercept requests, validate the DTO instance, and automatically throw an error if validation fails. This ensures that our service logic only receives clean, validated data.

Domain Enums for Type Safety and Consistency

Domain enums, like CaseStatus above, provide a set of named constants that represent a fixed range of values. Using TypeScript enums provides strong type checking at compile time, improving code readability and preventing errors from misspelled strings. Crucially, these enums can be directly mirrored in the database schema.

Prisma Schema Integration

Prisma, our ORM, allows us to define database schemas in a clear, declarative way. We can define enum types directly in schema.prisma and link them to model fields, ensuring that our application's domain logic (TypeScript enums) is perfectly synchronized with the database's data constraints.

// schema.prisma

enum CaseStatus {
  PENDING
  ACTIVE
  CLOSED
  ARCHIVED
}

model CourtCase {
  id          String       @id @default(uuid())
  title       String
  description String
  status      CaseStatus
  createdAt   DateTime     @default(now())
  updatedAt   DateTime     @updatedAt
}

When prisma migrate is run, this CaseStatus enum is created in the PostgreSQL database, enforcing data integrity at the database level as well. This dual enforcement provides a robust safety net for our application's data.

When This Approach Shines

This structured approach is invaluable in several scenarios:

  • Clear API Contracts: DTOs serve as unambiguous documentation for what your API expects and returns.
  • Reduced Boilerplate: Automated validation removes the need for repetitive manual checks within service methods.
  • Early Error Detection: Invalid data is caught at the API boundary, preventing it from corrupting application state or reaching the database.
  • Improved Type Safety: Enums and DTOs provide strong typing, reducing bugs and improving developer experience.
  • Enhanced Maintainability: Consistent data structures and validation rules make the codebase easier to understand, test, and refactor.

The Key Question

How can we ensure our application's data is always valid and consistent from the moment it enters our system until it's persisted? By proactively defining and validating data structures using DTOs, enforcing domain rules with enums, and synchronizing these definitions with a robust database schema via tools like Prisma. This foundational work builds a more resilient and maintainable application.


Generated with Gitvlg.com

Building Robust APIs with DTOs, Validation, and Prisma Enums in NestJS
N

Nacho

Author

Share: