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 ExpressAdapter in server/src/bootstrap.ts
  • SQLite: Embedded database using better-sqlite3 with the main data file at data/travel.db
  • WebSocket: Real-time collaboration via the ws library 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/health health checks
  • OIDC and MCP well-known metadata at /.well-known/*
  • OAuth 2.1 authorization and registration handlers
  • /mcp endpoint 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_KEY environment 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:

  1. Bootstrap: buildApp() creates the Nest application with Express adapter
  2. Middleware: Global security headers and logging applied to Express instance
  3. Platform Routes: Upload directories, health checks, and OAuth endpoints mounted
  4. Static Assets: Production client files registered
  5. Controller Registration: await app.init() activates all Nest controllers
  6. 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.ts applies Express routes for static files and OAuth endpoints before initializing Nest controllers via app.init()
  • Domain modules like AuthModule, CollabModule, and PhotosModule encapsulate business logic under server/src/nest/app.module.ts
  • Real-time features use the ws library attached to the same HTTP server, with authentication handled in server/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 the EncryptionKey provider

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:

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 →