≡ Menu

When you are building a modern backend, CRUD (Create, Read, Update, Delete) forms the bedrock of almost every application.

While frameworks like Express make you piece together your own routing and controller structures, NestJS provides a standardized, structural architecture right out of the box.

In this tutorial, we will build a clean, fully functional CRUD API for a “Products” resource using NestJS.

The NestJS Blueprint

Before writing code, it helps to understand how data flows through a NestJS application. Nest relies on a clear separation of concerns, dividing responsibilities into three core building blocks:

  • Modules: Containers that organize and group related code (Controllers and Services) together.

  • Controllers: Route handlers responsible for receiving incoming HTTP requests and sending back responses.

  • Services (Providers): The dedicated layer for your business logic, such as data validation, database queries, or calculations.

1. Project Setup and Scaffolding

Instead of creating files manually, we will use the powerful Nest CLI to scaffold our project and resource boilerplate instantly.

# Install the NestJS CLI globally
npm i -g @nestjs/cli

# Create a new project
nest new nestjs-crud-api
cd nestjs-crud-api

# Automatically generate a full CRUD resource for 'products'
nest g resource products

When prompted, choose REST API and select Yes to generating CRUD entry points.

The CLI will generate a beautifully structured src/products folder containing your module, controller, service, and data transfer objects (DTOs).

2. Defining the Product Interface

For this simple in-memory tutorial, let’s establish what a Product looks like. Create or update the src/products/entities/product.entity.ts file:

export class Product {
  id: string;
  title: string;
  price: number;
  inStock: boolean;
}

3. Handling Incoming Data with DTOs

Data Transfer Objects (DTOs) define the schema of the data coming into your API. They ensure your endpoints receive exactly what they expect.

Create Product DTO

Update src/products/dto/create-product.dto.ts:

export class CreateProductDto {
  title: string;
  price: number;
  inStock: boolean;
}

Update Product DTO

NestJS automatically maps your update DTO using PartialType, meaning all fields from the create schema become optional for PATCH requests. Check your src/products/dto/update-product.dto.ts:

 

import { PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';

export class UpdateProductDto extends PartialType(CreateProductDto) {}

4. Writing the Business Logic (The Service)

The service handles the state and data mutations.

Open src/products/products.service.ts and replace the boilerplate with an in-memory array implementation:

import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { Product } from './entities/product.entity';

@Injectable()
export class ProductsService {
  private products: Product[] = [];

  // CREATE
  create(createProductDto: CreateProductDto): Product {
    const newProduct: Product = {
      id: Math.random().toString(36).substring(2, 9), // Simple ID generator
      ...createProductDto,
    };
    this.products.push(newProduct);
    return newProduct;
  }

  // READ (All)
  findAll(): Product[] {
    return this.products;
  }

  // READ (One)
  findOne(id: string): Product {
    const product = this.products.find((p) => p.id === id);
    if (!product) {
      throw new NotFoundException(`Product with ID "${id}" not found`);
    }
    return product;
  }

  // UPDATE
  update(id: string, updateProductDto: UpdateProductDto): Product {
    const product = this.findOne(id);
    const index = this.products.findIndex((p) => p.id === id);
    
    this.products[index] = { ...product, ...updateProductDto };
    return this.products[index];
  }

  // DELETE
  remove(id: string): { deleted: boolean } {
    this.findOne(id); // Throws 404 if it doesn't exist
    this.products = this.products.filter((p) => p.id !== id);
    return { deleted: true };
  }
}

5. Exposing the Endpoints (The Controller)

NestJS uses routing decorators to map HTTP verbs directly to service methods.

Your src/products/products.controller.ts acts as the traffic controller:

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';

@Controller('products') // Base route: /products
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}

  @Post()
  create(@Body() createProductDto: CreateProductDto) {
    return this.productsService.create(createProductDto);
  }

  @Get()
  findAll() {
    return this.productsService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.productsService.findOne(id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateProductDto: UpdateProductDto) {
    return this.productsService.update(id, updateProductDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.productsService.remove(id);
  }
}

Summary of Completed Endpoints

With your server running via npm run start:dev, you can now test your API using tools like Postman, Bruno, or cURL against these routes:

HTTP Method Endpoint Description Expected Payload
POST /products Creates a new product { "title": "Keyboard", "price": 120, "inStock": true }
GET /products Retrieves all products None
GET /products/:id Retrieves a single product by ID None
PATCH /products/:id Updates specific fields of a product { "price": 99 }
DELETE /products/:id Deletes a product by ID None

By utilizing NestJS’s structured framework, you have built an API layer that is decoupled, highly testable, and naturally scalable as your data requirements grow.

Coding Quote of the Day:

“Clean code always looks like it was written by someone who cares.”

Michael Feathers

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }

In the world of NestJS, Data Transfer Objects (DTOs) are often treated as a boilerplate necessity—a simple class we create to satisfy the compiler.

However, if you are building complex systems like multi-tenant e-commerce platforms or AI-integrated backends, your DTOs are actually the first line of defense for your application’s integrity.

Why DTOs Matter More Than Ever

A DTO defines how data is sent over the network. By using classes coupled with decorators, NestJS allows for runtime validation, ensuring that the “junk data” never even hits your service layer.

The Standard Approach: Class-Validator

Most developers start with class-validator. It’s intuitive and integrates natively with the NestJS ValidationPipe.

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

export class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @MinLength(8)
  password: string;
}

While effective, this approach can sometimes lead to “Decorator Hell,” where your DTO classes become bloated with metadata that is hard to reuse outside of the NestJS context.


The “Zod-First” Revolution

As we move toward more unified architectures (like monorepos), many developers are shifting toward Zod for DTO validation. The “Zod-first” approach allows you to define a single schema that can be shared between your backend validation and your frontend forms (e.g., in a Next.js storefront).

Implementing Zod DTOs in NestJS

To use Zod, you can leverage the nestjs-zod library or create a custom pipe. This allows for incredibly powerful, functional validation logic that feels more like TypeScript and less like annotation.

import { createZodDto } from 'nestjs-zod';
import { z } from 'zod';

const CreateStoreSchema = z.object({
  name: z.string().min(3).max(50),
  subdomain: z.string().toLowerCase().regex(/^[a-z0-9-]+$/),
  plan: z.enum(['LAUNCH', 'SCALE', 'ENTERPRISE']),
});

export class CreateStoreDto extends createZodDto(CreateStoreSchema) {}

Key Benefits:

  • Deep Type Safety: Infers TypeScript types directly from the schema.

  • Transformation: Automatically handles data casting (e.g., converting a string to a Date object).

  • Shared Logic: Use the exact same CreateStoreSchema in your frontend dashboard to show real-time validation errors.


DTO Strategies for Multi-Tenant Systems

When building platforms that handle sensitive data across different “tenants” or “merchants,” your DTOs should be context-aware.

1. Partial Types for Updates

Don’t rewrite your DTOs for update operations. Use the PartialType utility to keep your code DRY (Don’t Repeat Yourself).

export class UpdateStoreDto extends PartialType(CreateStoreDto) {}

2. Stripping Sensitive Fields

Always ensure your ValidationPipe is configured to whitelist: true. This prevents “over-posting,” where a malicious user tries to inject a role: 'admin' field into a DTO that shouldn’t have it.

3. Handling AI Metadata

If your application integrates AI agents, your DTOs may need to handle structured “tool-calling” data. Using Zod’s passthrough() or strict() modes helps you control exactly how much “extra” data an AI agent is allowed to send to your API.


Best Practices for 2026

  • Always use Classes: Even if using Zod, wrap schemas in classes so the NestJS IoC container can recognize them for Dependency Injection and Swagger documentation.

  • Map, Don’t Leak: Use DTOs to map your Database Entities to API Responses. Never return your raw Database Entity (with password hashes or internal IDs) directly to the client.

  • Automate Documentation: Ensure your DTOs are decorated for @nestjs/swagger. This turns your DTOs into a living API contract that frontend teams (and AI agents) can read easily.

Choosing a Validation Library for NestJS

Choosing between class-validator and Zod often comes down to your project’s architecture and how much you value code sharing.

Class-Validator

  • Syntax: Uses Decorators and Annotations directly on your class properties.

  • Type Inference: Mostly manual; you must define the class and the types separately.

  • Frontend Sharing: Difficult, as it relies on NestJS-specific decorators that don’t always play nice with frontend frameworks like React or Vue.

  • Complexity: Simple and intuitive for those used to Object-Oriented Programming.


Zod

  • Syntax: Uses a Functional Schema approach to define data shapes.

  • Type Inference: Automatic; the TypeScript types are inferred directly from your schema definitions.

  • Frontend Sharing: Seamless, making it the “gold standard” for monorepos where the same schema validates both your frontend forms and backend API.

  • Complexity: Highly Powerful, offering advanced features like data transformation and complex refinements.

By mastering DTOs, you aren’t just “fixing types”—you’re building a secure, predictable, and scalable gateway for your entire application.

Quote of the Day:

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”John Woods

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }