TREK Server Architecture: NestJS and Express Implementation Guide
TREK is built as a single NestJS application running on an Express adapter, combining TypeScript controllers with classic Express middleware to serve APIs, static assets, and real-time WebSocket connections.
The TREK server architecture powers an open-source travel planning application built by mauriceboe/TREK. This monolithic yet modular design leverages NestJS v11 as its core framework while utilizing Express for low-level HTTP handling, creating a hybrid approach that supports both modern dependency injection and traditional middleware patterns.
Technology Stack and Core Dependencies
TREK runs on Node.js v22 with a carefully selected stack optimized for type safety and performance:
- NestJS v11: The core framework providing decorators, dependency injection, and modular architecture
- Express: The underlying HTTP server accessed via
ExpressAdapterinserver/src/bootstrap.ts - SQLite: Embedded database using
better-sqlite3with the main data file atdata/travel.db - WebSocket: Real-time collaboration via the
wslibrary attached to the same HTTP server - Authentication: JWT sessions, OIDC SSO, WebAuthn (Passkeys), and TOTP 2FA
- MCP: Model-Context-Protocol server implementing OAuth 2.1 for AI tool integration
The front-end utilizes React 19 with Vite, while the server focuses exclusively on API delivery and static asset management.
Bootstrap and Application Initialization
The server entry point in server/src/bootstrap.ts orchestrates the entire startup sequence through the buildApp() function. This async factory creates the Nest application using an Express adapter, then layers middleware in a specific order before initializing controllers.
// server/src/bootstrap.ts
export async function buildApp(): Promise<INestApplication> {
const app = await NestFactory.create(AppModule, new ExpressAdapter());
const instance = app.getHttpAdapter().getInstance();
// 1️⃣ Global middleware (CSP, CORS, HSTS, logging, …)
applyGlobalMiddleware(instance, { bodyParser: false });
// 2️⃣ Static uploads (avatars, covers, journey photos)
applyPlatformUploads(instance);
// 3️⃣ Transport routes (health, OAuth/MCP, well‑known metadata)
applyPlatformTransport(instance);
// 4️⃣ Static client assets (served only in production)
applyPlatformStatic(instance);
// 5️⃣ All Nest controllers are finally registered
await app.init();
return app;
}
The ordering is critical. Express routes that must be reachable before Nest's router—such as static files and health checks—are mounted prior to await app.init(). This ensures direct file serving and OAuth endpoints short-circuit before reaching Nest's controller routing logic.
Module Hierarchy and Domain Structure
The root AppModule in server/src/nest/app.module.ts imports domain-specific modules and registers global filters and interceptors that apply across the entire API surface.
// server/src/nest/app.module.ts
@Module({
imports: [
DatabaseModule,
WeatherModule,
AirportsModule,
ConfigModule,
SystemNoticesModule,
MapsModule,
AuthModule,
OidcModule,
OauthModule,
AdminModule,
AddonsModule,
BookingImportModule,
],
controllers: [HealthController],
providers: [
HealthService,
{ provide: APP_FILTER, useClass: TrekExceptionFilter },
{ provide: APP_FILTER, useClass: SpaFallbackFilter },
{ provide: APP_INTERCEPTOR, useClass: IdempotencyInterceptor },
],
})
export class AppModule {}
Key domain modules live under server/src/<domain>/ and encapsulate specific business logic:
- AuthModule: JWT login, password reset, and session cookie management
- OidcModule: OpenID Connect SSO integration for external providers
- OauthModule: OAuth 2.1 endpoints supporting the MCP implementation
- CollabModule: Real-time chat, notes, and polling features
- PhotosModule: Integration with Immich and Synology for photo storage
- BackupModule: Scheduled SQLite database backups and restore operations
Express Platform Routes and Static Handling
Static asset serving and transport endpoints are defined in server/src/nest/platform/platform.routes.ts, separate from Nest controllers. These Express routes handle infrastructure concerns:
applyPlatformUploads: Serves /uploads/avatars, /uploads/covers, and /uploads/journey as public static directories. Photo downloads at /uploads/photos/:filename are guarded by JWT or share-token validation in server/src/middleware/auth.ts.
applyPlatformTransport: Registers critical infrastructure endpoints including:
/api/healthhealth checks- OIDC and MCP well-known metadata at
/.well-known/* - OAuth 2.1 authorization and registration handlers
/mcpendpoint supporting POST, GET, and DELETE methods for the Model-Context-Protocol
applyPlatformStatic: In production environments, serves the built React client from the public/ directory with no-store cache headers on index.html to prevent SPA routing issues.
Real-Time Collaboration via WebSocket
TREK implements real-time synchronization through a WebSocket server defined in server/src/websocket.ts. The ws library attaches to the same HTTP server created by Nest's Express adapter, allowing authenticated socket connections to broadcast trip updates instantly to all participants.
This design avoids the overhead of separate socket servers while maintaining integration with Nest's authentication middleware. Socket connections validate JWT tokens during the handshake process, ensuring only authorized trip participants receive collaborative updates.
Authentication and Security Layers
The TREK server architecture implements defense in depth through multiple authentication strategies:
- JWT Sessions: Signed with the
ENCRYPTION_KEYenvironment variable, stored in HTTP-only cookies - TOTP 2FA: Time-based one-time passwords with backup code recovery
- WebAuthn Passkeys: Passwordless authentication toggleable via admin settings
- OIDC SSO: Configurable providers including Google, Apple, and Keycloak through
OIDC_*environment variables - Global Middleware: Helmet-managed CSP headers, CORS configuration, HSTS enforcement, and request logging applied via
applyGlobalMiddleware()in the bootstrap sequence
The MCP (Model-Context-Protocol) endpoints under /mcp reuse the same JWT/refresh-token flow, ensuring consistent security across both human and AI client access.
Data Layer and Encryption
SQLite serves as the single source of truth, managed through DatabaseModule in server/src/database/database.module.ts. The module provides a singleton database instance wrapping better-sqlite3 for synchronous query execution.
Sensitive data protection relies on the EncryptionKey system, which encrypts at-rest secrets including API keys and MFA seeds. The migrate-encryption.ts script handles key rotation without service interruption.
Add-On System and Feature Toggling
TREK supports runtime feature toggling through an add-on system checked via isAddonEnabled(ADDON_IDS.<NAME>). Admin users enable features like MCP, Atlas, or Vacay through the admin UI, with states stored in the database.
This architecture prevents feature fingerprinting by ensuring disabled routes return 404 errors rather than exposing authentication requirements. The design keeps the core API stable while allowing optional modules to load dynamically without code changes.
Deployment and Startup Flow
The complete startup sequence follows this pattern:
- Bootstrap:
buildApp()creates the Nest application with Express adapter - Middleware: Global security headers and logging applied to Express instance
- Platform Routes: Upload directories, health checks, and OAuth endpoints mounted
- Static Assets: Production client files registered
- Controller Registration:
await app.init()activates all Nest controllers - WebSocket Attachment: Real-time server binds to the HTTP listener
Deployment utilizes Docker with the ENCRYPTION_KEY environment variable:
ENCRYPTION_KEY=$(openssl rand -hex 32) docker run -d -p 3000:3000 \
-e ENCRYPTION_KEY=$ENCRYPTION_KEY \
-v ./data:/app/data -v ./uploads:/app/uploads mauriceboe/trek
Extending the Architecture
Adding new functionality requires minimal changes to the core infrastructure. Create a module following Nest conventions:
// src/myfeature/myfeature.module.ts
import { Module } from '@nestjs/common';
import { MyFeatureController } from './myfeature.controller';
import { MyFeatureService } from './myfeature.service';
@Module({
controllers: [MyFeatureController],
providers: [MyFeatureService],
})
export class MyFeatureModule {}
Then import it in server/src/nest/app.module.ts to automatically register the new API routes within the unified Nest application without modifying Express configuration.
Summary
- TREK server architecture combines NestJS v11 with an Express adapter, creating a hybrid monolith that leverages modern dependency injection while maintaining traditional middleware capabilities
- Bootstrap sequence in
server/src/bootstrap.tsapplies Express routes for static files and OAuth endpoints before initializing Nest controllers viaapp.init() - Domain modules like
AuthModule,CollabModule, andPhotosModuleencapsulate business logic underserver/src/nest/app.module.ts - Real-time features use the
wslibrary attached to the same HTTP server, with authentication handled inserver/src/websocket.ts - Security implements layered authentication including JWT, OIDC, WebAuthn, and TOTP, with global middleware enforcing CSP and CORS policies
- Data persistence relies on SQLite through
DatabaseModule, with at-rest encryption managed via theEncryptionKeyprovider
Frequently Asked Questions
What core technologies power the TREK server architecture?
The TREK server is built as a NestJS v11 application running on Node.js v22, utilizing an Express adapter for HTTP handling. It uses SQLite as its primary database via the better-sqlite3 library, and implements real-time collaboration through the WebSocket (ws) library attached to the same server instance.
How does TREK handle static file serving alongside API routes?
Static files are served through Express middleware registered in server/src/nest/platform/platform.routes.ts before the Nest application initializes. The applyPlatformUploads() and applyPlatformStatic() functions mount directories like /uploads/avatars and the production client build prior to await app.init(), ensuring these routes short-circuit before reaching Nest's controller routing.
What authentication methods does the TREK server support?
The architecture supports JWT sessions with HTTP-only cookies, TOTP 2FA with backup codes, WebAuthn Passkeys for passwordless login, and OIDC SSO integration for external providers like Google and Keycloak. All methods are implemented through modular providers in AuthModule and OidcModule, with validation logic centralized in server/src/middleware/auth.ts.
How does the add-on system work in TREK's modular architecture?
Add-ons are runtime features toggled via the admin UI and stored in the SQLite database. The server checks isAddonEnabled(ADDON_IDS.<NAME>) before exposing routes, ensuring disabled features return 404 responses. This design keeps the core API stable while allowing optional modules like MCP or Atlas to load dynamically without requiring application restarts or code changes.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →