≡ Menu

In the pursuit of technical mastery, we often find that the “standard” way of doing things eventually becomes the very thing holding us back. For those of us building complex, multi-tenant systems—platforms designed to challenge the status quo of content management—the traditional NestJS validation stack can start to feel like a collection of fragile abstractions.

If you are engineering a system where data integrity is the bedrock of “Agentic Commerce,” you need a validation strategy that is as disciplined as it is flexible. It is time to move beyond the decorator-heavy world of class-validator and embrace a Zod-first architecture. This shift isn’t just about a library change; it’s about establishing a single source of truth that spans your entire monorepo, from the deep backend to the storefront editor.


The Architecture of Certainty

When building a modern CMS, you aren’t just handling strings and numbers; you are handling the lifeblood of a merchant’s business. Traditional validation often leads to a “double-entry” problem: you define a class for the runtime and an interface for the compiler. This redundancy is where bugs thrive.

Zod eliminates this friction. By making the schema the foundation, you gain a functional, declarative contract that ensures your data is exactly what you think it is, exactly when you need it to be.

1. Defining the Shared Contract

In a monorepo, your validation logic should live in a shared library. This ensures your Next.js dashboard and NestJS API are always in perfect sync.

// apps/shared/schemas/content.schema.ts
import { z } from 'zod';

// The single source of truth for a CMS Post
export const PostSchema = z.object({
  title: z.string().min(5).max(100),
  slug: z.string().toLowerCase().regex(/^[a-z0-9-]+$/),
  content: z.record(z.any()), // Flexible for block-based editors
  status: z.enum(['draft', 'published', 'archived']),
});

// TypeScript inference: No manual interface needed
export type PostDto = z.infer<typeof PostSchema>;

2. The Validation Engine

To integrate this into NestJS, we implement a custom pipe. This replaces the “magic” of the global ValidationPipe with explicit, predictable logic.

// apps/backend/src/common/pipes/zod-validation.pipe.ts
import { PipeTransform, BadRequestException } from '@nestjs/common';
import { ZodSchema } from 'zod';

export class ZodValidationPipe implements PipeTransform {
  constructor(private schema: ZodSchema) {}

  transform(value: unknown) {
    const result = this.schema.safeParse(value);
    
    if (!result.success) {
      // Return structured errors perfect for a CMS dashboard UI
      throw new BadRequestException(result.error.format());
    }
    
    return result.data;
  }
}

3. Clean Controllers

With the pipe in place, your controllers become clean and focused on orchestration rather than data checking.

// apps/backend/src/modules/posts/posts.controller.ts
import { Controller, Post, Body, UsePipes } from '@nestjs/common';
import { PostSchema, PostDto } from '@shared/schemas/content.schema';
import { ZodValidationPipe } from '../../common/pipes/zod-validation.pipe';

@Controller('posts')
export class PostsController {
  
  @Post()
  @UsePipes(new ZodValidationPipe(PostSchema))
  async create(@Body() createPostDto: PostDto) {
    // createPostDto is 100% validated and typed here
    return this.postsService.create(createPostDto);
  }
}

4. Guarding the Exit: Response Serialization

In a multi-tenant environment, what you don’t send is as important as what you do. Zod allows you to explicitly shape your outgoing data.

// apps/backend/src/modules/posts/posts.service.ts

async getPublicPost(slug: string) {
  const rawData = await this.db.post.findUnique({ where: { slug } });
  
  // Strip internal tenant IDs or metadata before it hits the storefront
  const PublicPostSchema = PostSchema.extend({ id: z.string() });
  
  return PublicPostSchema.parse(rawData);
}

The Mamba Mentality in Code

Refactoring a core system to a Zod-first approach is an act of engineering discipline.

It is about stripping away the “magic” and replacing it with mastery.

By decoupling your validation from the framework and centering it around your data contracts, you build a system that is prepared for the scale of tomorrow.

Whether you are building the next generation of e-commerce or a decentralized content engine, your foundation is only as strong as your types.

Embrace the schema, eliminate the redundancy, and keep building.

What part of your stack will you refactor next?

Quote of the Day:

“First, solve the problem. Then, write the code.”John Johnson

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 }

As your Next.js application scales, your app directory can quickly transform from a neat list of pages into a complex maze of folders.

You might find yourself wanting to organize files by feature or intent—like grouping all “Auth” pages together—without those folder names actually appearing in your website’s URL.

This is exactly where Route Groups come in.

They allow you to wrap folders in parentheses, signaling to Next.js: “Organize these files here, but keep this folder out of the URL.”


The Anatomy of a Route Group

To create a route group, you simply wrap a folder name in parentheses: (folderName).

  • Standard Folder: app/marketing/about/page.tsx → URL: /marketing/about

  • Route Group: app/(marketing)/about/page.tsx → URL: /about

By using (marketing), you’ve grouped your marketing-related routes for your own sanity, but the end user never sees the word “marketing” in their browser bar.


Why Use Route Groups?

Beyond just keeping your files tidy, route groups unlock powerful architectural patterns:

1. Logical Organization

The most common use case is simply grouping related routes (e.g., (auth), (dashboard), (shop)) so they aren’t scattered alphabetically throughout your root app folder.

2. Creating Multiple Root Layouts

Usually, Next.js expects one top-level layout.tsx in the root of the app directory.

However, what if your marketing site needs a standard navbar, but your user dashboard needs a completely different sidebar and no navbar at all?

By moving your routes into separate groups, you can give each group its own Root Layout.

  • app/(marketing)/layout.tsx (Applies to all marketing pages)

  • app/(dashboard)/layout.tsx (Applies to all dashboard pages)

Note: When you opt for multiple root layouts, the top-level app/layout.tsx is removed. Each root layout must then include its own <html> and <body> tags.

3. Opting-in to Shared UI

You might want a specific set of routes to share a layout while others don’t. For instance, you could have (shop)/layout.tsx that provides a persistent shopping cart view for all routes inside that folder, while your (legal) group remains a clean, distraction-free layout for Terms of Service and Privacy pages.


Essential Rules to Remember

  1. URL Neutrality: The name of the group (e.g., (marketing)) is completely omitted from the URL path.

  2. Naming Collisions: Be careful! Since the group name is ignored, app/(groupA)/about/page.tsx and app/(groupB)/about/page.tsx will both resolve to /about, causing a build error.

  3. Nested Groups: You can nest route groups within each other if your project is particularly massive, though usually, one level is enough for most apps.

Feature Comparison

Feature: Regular Folder vs. Route Group

Visible in URL: Yes (Regular) | No (Route Group)

Can have a Layout: Yes (Regular) | Yes (Route Group)

Purpose: Path Definition (Regular) | Organization & Layout Isolation (Route Group)

Special Syntax: folder (Regular) | (folder) (Route Group

Conclusion

Route Groups are a “quality of life” feature that separates your file-system architecture from your public URL structure.

Whether you’re trying to isolate a specific layout or just want to group your “Admin” tools together without cluttering your URLs, route groups provide the flexibility to build a scalable, maintainable Next.js application.

Quote of the Day:

“For every minute spent organizing, an hour is earned.” — Benjamin Franklin

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 }