Design Principles Followed in TREK Server Development: A Modular NestJS Architecture

TREK’s server architecture implements domain-driven modularity, dependency injection, and schema-first validation using NestJS, while preserving legacy-compatible error formats through a global exception filter.

TREK is a travel management application structured as a monorepo that modernizes legacy Express patterns through a sophisticated NestJS implementation. The server development follows specific design principles that ensure type safety, testability, and backward compatibility with existing clients. These principles manifest throughout the codebase, from the domain-specific module organization in server/src/nest/ to the shared Zod schemas consumed by both client and server through the @trek/shared package.

Domain-Driven Modularity and Service Isolation

The architecture organizes code around business capabilities rather than technical layers, ensuring each domain encapsulates its own routing logic and data persistence.

Business Domain Controllers and Services

Each business area maintains isolated modules with dedicated controllers and services. The trips domain uses server/src/nest/trips/trips.controller.ts paired with server/src/services/tripService.ts, while the vacations domain follows the same pattern in server/src/nest/vacay/vacay.controller.ts and server/src/services/vacayService.ts. Authentication concerns remain similarly isolated in server/src/nest/auth/auth.controller.ts and server/src/services/authService.ts, preventing cross-domain coupling.

// server/src/nest/trips/trips.controller.ts
import { Controller, Post, Body, UsePipes } from '@nestjs/common';
import { ZodValidationPipe } from '../common/zod-validation.pipe';
import { createTripSchema } from '@trek/shared/src/trip/trip.schema';
import { TripService } from '../../services/tripService';

@Controller('trips')
export class TripsController {
  constructor(private readonly trips: TripService) {}

  @Post()
  @UsePipes(new ZodValidationPipe(createTripSchema))
  async create(@Body() dto: any) {
    return this.trips.createTrip(dto);
  }
}

Dependency Injection and Clean Separation of Concerns

TREK leverages NestJS’s built-in dependency injection container to decouple HTTP handling from business operations, keeping controllers thin and services testable.

Injectable Services and Constructor Injection

Services declare their injectable status using the @Injectable() decorator, as seen in server/src/services/authService.ts. Controllers receive these services through constructor injection without direct instantiation. The TripsController demonstrates this pattern with constructor(private readonly trips: TripService) {}, while the underlying TripService in server/src/services/tripService.ts handles Prisma database interactions independently.

// server/src/services/tripService.ts
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class TripService {
  private prisma = new PrismaClient();

  async createTrip(data: any) {
    return this.prisma.trip.create({ data });
  }
}

Validation and Error Handling Pipelines

Request processing follows a pipeline pattern where dedicated pipes and filters handle cross-cutting concerns. The ZodValidationPipe in server/src/nest/common/zod-validation.pipe.ts validates incoming payloads against Zod schemas, while the TrekExceptionFilter in server/src/nest/common/trek-exception.filter.ts manages error formatting. This keeps controllers focused solely on delegation to injected services.

Legacy-Compatible Error Envelope

Despite the modern NestJS foundation, TREK maintains backward compatibility with the original Express application's error response format through a global exception handling strategy.

Global Exception Filter Implementation

The TrekExceptionFilter intercepts all exceptions and normalizes them into the historic { error: … } JSON structure. Registered globally in server/src/bootstrap.ts via app.useGlobalFilters(new TrekExceptionFilter()), this filter ensures that validation failures, database errors, and unexpected exceptions all return the consistent error shape expected by legacy clients.

// server/src/bootstrap.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TrekExceptionFilter } from './nest/common/trek-exception.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new TrekExceptionFilter());
  await app.listen(3000);
}
bootstrap();

Schema-First Validation and Type Safety

Validation occurs at the boundary using strict schemas defined in the shared package, ensuring runtime type safety matches compile-time expectations across the monorepo.

Zod Schema Integration

The ZodValidationPipe imports ZodType from @trek/shared and validates request bodies against schemas like createTripSchema before they reach service methods. This schema-first approach, combined with the monorepo's shared TypeScript definitions in the shared/src/ directory, eliminates type drift between client forms and server APIs.

Security Middleware and Idempotency

Custom Express-style middlewares provide defense-in-depth for security and request integrity, integrating with NestJS guards to enforce policies before route handlers execute.

SSRF Protection and Request Deduplication

The ssrfGuard middleware in server/src/middleware/ssrfGuard.ts inspects user-provided URLs to prevent Server-Side Request Forgery attacks, rejecting external URLs with a 400 status and { error: 'External URLs are not allowed' } response. The idempotency middleware, tested in server/src/middleware/idempotency.test.ts, guards against duplicate request processing using custom logic to track request signatures.

// server/src/middleware/ssrfGuard.ts
import { Request, Response, NextFunction } from 'express';
import { isExternalUrl } from '../utils/url';

export function ssrfGuard(req: Request, res: Response, next: NextFunction) {
  const url = req.body?.url;
  if (url && isExternalUrl(url)) {
    return res.status(400).json({ error: 'External URLs are not allowed' });
  }
  next();
}

Centralized Configuration and Background Processing

Environment management and scheduled tasks follow explicit, injectable patterns that keep secrets out of business logic.

ConfigModule for Environment Management

Rather than scattering process.env accesses, TREK uses server/src/nest/config/config.module.ts to load and validate configuration once, injecting values into services via NestJS's configuration provider. This centralizes environment variable handling and enables validation of required variables at application startup.

Cron-Based Job Scheduling

The server/src/scheduler.ts file implements a lightweight task runner using cron expressions derived from settings. This handles periodic maintenance tasks like automatic backups without blocking the main HTTP event loop, reading configuration values from the centralized ConfigModule to determine execution intervals.

Summary

  • Domain-driven modularity organizes code by business capability (trips, vacations, auth) with dedicated controllers in server/src/nest/ and services in server/src/services/.
  • Dependency injection via @Injectable() decorators and constructor injection keeps controllers thin and services isolated from HTTP concerns.
  • Schema-first validation using Zod schemas from @trek/shared ensures type-safe inputs across the monorepo through the ZodValidationPipe.
  • Legacy-compatible error handling through TrekExceptionFilter maintains the { error: … } response format from the original Express implementation.
  • Security middleware including ssrfGuard and idempotency checks in server/src/middleware/ protect against common attack vectors.
  • Centralized configuration in config.module.ts and background jobs in scheduler.ts complete the layered, testable architecture.

Frequently Asked Questions

What architectural pattern does TREK server use?

TREK follows a layered, domain-driven architecture built on NestJS. The codebase separates concerns into distinct layers: controllers handling HTTP requests in server/src/nest/*/, services containing business logic in server/src/services/, pipes managing validation, and filters processing exceptions. This modularity allows each domain to evolve independently while sharing common infrastructure like the ZodValidationPipe and TrekExceptionFilter.

How does TREK maintain backward compatibility during its NestJS migration?

TREK preserves compatibility through the TrekExceptionFilter in server/src/nest/common/trek-exception.filter.ts, which catches all exceptions and formats them into the { error: … } JSON structure used by the legacy Express application. This global filter, registered in server/src/bootstrap.ts, ensures that client error handling code continues to function unchanged despite the underlying framework switch.

Why does TREK use Zod for validation instead of NestJS's built-in ValidationPipe?

TREK implements a schema-first validation strategy using Zod because it shares these schemas between client and server through the @trek/shared package. The custom ZodValidationPipe in server/src/nest/common/zod-validation.pipe.ts validates requests against these shared schemas, guaranteeing end-to-end type safety and preventing validation logic from diverging between frontend forms and backend APIs.

Where are background tasks handled in the TREK server?

Background processing occurs in server/src/scheduler.ts, which runs cron-based jobs for tasks like automatic backups. This keeps the main HTTP event loop unblocked while ensuring periodic maintenance executes reliably. The scheduler reads configuration values from the centralized ConfigModule in server/src/nest/config/config.module.ts to determine execution intervals.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →