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 };
}
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;
}
}
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),
}),
},
});
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
andnpm 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
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)
If you want to know more about the industry and technical challenges we have, feel free to contact us. Greetings SJ