DEV Community

BB Dev Team
BB Dev Team

Posted on

Security by Design with NestJS, zod, ts-rest, and Custom In-House Frameworks

When you build a platform that handles millions of users and sensitive data, security and reliability are not just features – they’re survival requirements.

In our engineering team, we rely on NestJS (backend) and Next.js (frontend), but we don’t stop there. To ensure maximum security and resilience, we combine battle-tested open-source tools with custom in-house frameworks designed specifically for our threat model and performance needs.

This post gives an overview of how we approach data protection, authentication, validation, system reliability, secure real-time communication, and code security at scale.


Core Security Principles

1) Minimal Data Collection

  • Every extra field increases risk.
  • For KYC, we persist only the legally required attributes and discard everything else.

2) Encryption Everywhere

  • At Rest: All DB fields with sensitive data are encrypted.
  • In Transit: TLS 1.3 + HSTS with automatic certificate rotation.
  • On Application Layer: Some values (like ID scans) are encrypted before they ever reach the database.
import * as crypto from "crypto";

function encrypt(value: string, key: string) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv("aes-256-gcm", Buffer.from(key, "hex"), iv);
  let encrypted = cipher.update(value, "utf8", "hex");
  encrypted += cipher.final("hex");
  const authTag = cipher.getAuthTag().toString("hex");
  return { iv: iv.toString("hex"), data: encrypted, tag: authTag };
}
Enter fullscreen mode Exit fullscreen mode

3) Strong Authentication (2FA)

  • Mandatory 2FA for users and admins.
  • NestJS Guards make it straightforward to enforce OTP/WebAuthn or second-factor checks on sensitive routes.
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";

@Injectable()
export class TwoFactorGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToHttp().getRequest();
    return req.user?.is2FAAuthenticated === true;
  }
}
Enter fullscreen mode Exit fullscreen mode

4) Role-Based Access Control

  • Implemented via NestJS Guards and decorators.
  • Principle: Least Privilege – narrow, task-focused scopes.

5) Auditing & Monitoring

  • Centralized logging (e.g., ELK / Datadog) with anomaly detection.
  • Alerts on suspicious patterns (login abuse, unusual data exports).

Request & Response Validation with ts-rest + Zod

We use ts-rest combined with Zod to strictly validate both requests and responses. This enforces contracts between backend and frontend and prevents accidental leaks or schema mismatches.

import { initContract } from "@ts-rest/core";
import { z } from "zod";

const c = initContract();

export const authContract = c.router({
  login: {
    method: "POST",
    path: "/auth/login",
    responses: {
      200: z.object({
        accessToken: z.string(),
        refreshToken: z.string(),
      }),
    },
    body: z.object({
      email: z.string().email(),
      password: z.string().min(8),
    }),
  },
});
Enter fullscreen mode Exit fullscreen mode

Reliability: Microservices & Proxies (API Path)

Security without availability is meaningless. Our API services are designed for fault tolerance:

  • Microservices: Isolated domain services reduce blast radius and allow independent scaling.
  • Proxies & API Gateway: Centralized entry for routing, rate limiting, schema validation, and WAF.
  • Graceful Degradation: If one service fails, proxies reroute/fallback without exposing raw errors.
  • Zero-Downtime Deployments with blue-green strategies and rolling updates.
  • Circuit Breakers & Retries with idempotency keys to avoid duplicate side effects.

Note: The real-time stack (Socket.IO) is separate from the microservices runtime. See the next section.


Secure Real-Time Communication (Socket.IO Path)

We use a heavily adapted version of Socket.IO for customer chat and live website updates. This channel is independent from the microservices and optimized for reliability and security:

  • All socket connections are encrypted end-to-end.
  • Custom handshake validation prevents session hijacking.
  • Events are schema-validated before leaving or entering the server.
  • Proxies apply throttling and IP-based abuse detection.

Why We Use In-House Frameworks

While we like NestJS and Next.js, we intentionally reduce reliance on third-party open-source libraries.

  • Many open-source packages are under-maintained and accumulate vulnerabilities.
  • In-house frameworks allow us to:
    • Control the attack surface.
    • Patch vulnerabilities instantly.
    • Simplify dependency management.

We selectively integrate OSS only when the maintenance and security posture is strong – and even then, often behind our own abstraction layers.


UI: A Hardened Version of shadcn

For the frontend UI, we rely on a heavily customized version of shadcn/ui.

  • It gives us a consistent, accessible component system.
  • We stripped unnecessary dependencies and hardened components against XSS and injection risks.
  • Our version is tightly integrated with our own design system and security checks (e.g., CSP compliance).

Code Security: Obfuscation, Commit Rules & Tooling

We treat code as a security boundary itself:

  • Obfuscators: We run multiple stages of JS/TS obfuscation on build artifacts to hinder reverse engineering.
  • Commit Guidelines: Hard rules enforced via Git hooks – no secrets, no TODOs, no debug code in commits.
  • Automated Toolchain: Before deployment, every commit passes through a suite of security tools:
    • eslint with strict security configs
    • ts-prune to detect unused/forgotten code
    • depcheck and npm audit for dependency issues
    • semgrep for static code vulnerability scanning
    • zap-cli (OWASP ZAP) for lightweight API security scans in CI/CD
    • docker-bench-security for container hardening checks
    • Custom secret scanners to detect keys, tokens, or credentials in code

This ensures that only secure, hardened, and obfuscated code ever reaches production.


Architecture Overview

   [ User ] 
      │
      ▼
 [ Proxy / WAF ]
      │
 ┌────┴─────────┐
 ▼              ▼
[ Auth ]     [ API Gateway ]
   │              │
   ▼              ▼
[ Microservices ]       <-- encrypted APIs
   │
   ▼
[ Encrypted Database ]

   (Parallel Stack)
   [ User ]
      │
      ▼
 [ Secure Socket.IO ]  <-- chats & live updates
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Collect less, protect more – store the minimum data possible.
  • Encrypt at every layer – DB, transit, application.
  • 2FA everywhere – users and admins alike.
  • Validate requests & responses with ts-rest + Zod.
  • Design for failure – microservices + proxies keep the system resilient.
  • Harden real-time communication – encrypted and validated Socket.IO.
  • Limit external dependencies – in-house frameworks reduce attack surface.
  • UI is also security – a hardened design system prevents front-end exploits.
  • Secure the code itself – obfuscation, commit rules, and automated security tooling.

Security is never “done.” But by combining strong principles, carefully chosen tools, and a defense-in-depth strategy, you can significantly increase both trust and resilience.


We are the engineering team behind bb-escort.de. On dev.to we share lessons learned about scaling, security, and developer experience.

Top comments (1)

Collapse
 
bbescort-team profile image
BB Dev Team

If you want to know more about the industry and technical challenges we have, feel free to contact us. Greetings SJ