Files
twenty/packages/twenty-server/src/app.module.ts
T
Félix Malfait 5dfb66917c Upgrade NestJS from 10.x to 11.x (#15836)
## Overview
This PR upgrades all NestJS dependencies from version 10.x to 11.x,
following the [official migration
guide](https://docs.nestjs.com/migration-guide). This builds on top of
the v9 to v10 upgrade completed in PR #15835.

## Changes

### Dependencies Updated
**Core packages (10.x → 11.x):**
- `@nestjs/common`: 10.4.16 → 11.0.8
- `@nestjs/core`: 10.4.16 → 11.0.8
- `@nestjs/platform-express`: 10.4.16 → 11.0.8
- `@nestjs/config`: 3.2.3 → 3.3.0
- `@nestjs/passport`: 10.0.3 → 11.0.0
- `@nestjs/axios`: 3.0.2 → 3.1.2
- `@nestjs/schedule`: ^3.0.0 → ^4.1.1
- `@nestjs/serve-static`: 4.0.2 → 5.0.1
- `@nestjs/cache-manager`: ^2.2.1 → ^2.3.0
- `@nestjs/jwt`: 10.2.0 → 11.0.0
- `@nestjs/typeorm`: 10.0.2 → 11.0.0
- `@nestjs/terminus`: 11.0.0 (already on v11)
- `@nestjs/event-emitter`: 2.1.0 (compatible)

**DevDependencies:**
- `@nestjs/testing`: ^10.4.16 → ^11.0.8
- `@nestjs/schematics`: ^10.1.0 → ^11.0.2
- `@nestjs/cli`: 10.3.0 → 11.0.0

### Code Changes
**Fixed: TwentyConfigModule conditional imports**
- Updated `TwentyConfigModule.forRoot()` to use spread operator for
conditional imports
- Fixes TypeScript error with NestJS 11's stricter DynamicModule type
checking

**Cleanup: Removed unused package**
- Removed `@revertdotdev/revert-react` (not being used anywhere in the
codebase)

## Breaking Changes Addressed

### 1.  Reflector Type Inference
- **Impact**: None - codebase only uses `reflector.get()` method
- **Analysis**: Does not use `getAllAndMerge()` or `getAllAndOverride()`
(the methods with breaking changes)
- **Files reviewed**: feature-flag.guard.ts,
message-queue-metadata.accessor.ts,
workspace-query-hook-metadata.accessor.ts

### 2.  Lifecycle Hooks Execution Order
- **Change**: Termination hooks (`OnModuleDestroy`,
`BeforeApplicationShutdown`, `OnApplicationShutdown`) now execute in
REVERSE order
- **Analysis**: Reviewed all lifecycle hook implementations
  - Redis client cleanup
  - Database connection cleanup (GlobalWorkspaceDataSource)
  - BullMQ queue/worker cleanup
  - Cache storage cleanup
- **Result**: Dependency order is safe - services using connections
clean up before the connections themselves

### 3.  Middleware Registration Order
- **Change**: Global middleware now executes first regardless of import
order
- **Analysis**: Middleware is not registered as global, so execution
order remains consistent
- **Files reviewed**: app.module.ts, middleware.module.ts

## Testing

All tests passing and build successful:

**Unit Tests (283+ tests):**
-  Health module: 38 tests passed
-  Auth module: 115 tests passed (passport v11 integration)
-  REST API: 90 tests passed (middleware and express platform)
-  Feature flags: 17 tests passed (Reflector usage)
-  Workspace: 23 tests passed

**Build & Quality:**
-  Type checking: Passed
-  Linting: Passed
-  Build: 3,683 files compiled successfully

## Verification

Tested critical NestJS functionality:
-  Authentication & Security (JWT, OAuth, guards)
-  HTTP Platform (Express integration, REST endpoints)
-  Dependency Injection (Services, factories, providers)
-  Cache Management (Redis with @nestjs/cache-manager)
-  GraphQL (Query runners, resolvers)
-  Configuration (Environment config)
-  Scheduling (Cron jobs with @nestjs/schedule v4)
-  Lifecycle Hooks (Module initialization and cleanup)
-  Reflector (Metadata reflection in guards)

## Related PRs
- #15835 - Upgrade NestJS from 9.x to 10.x (completed)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Upgrades NestJS to v11 and updates routing patterns, auth strategies,
GraphQL schema options, and build/dist paths (scripts, Docker, Nx,
migrations, assets), plus enables Devtools in development.
> 
> - **Backend (NestJS 11 upgrade)**:
> - Bump `@nestjs/*` packages (core, platform-express, jwt, passport,
typeorm, serve-static, schedule, cli/testing/schematics) to v11.
> - Update REST/route-trigger/file controllers to new wildcard syntax
(`*path`).
> - Refactor OAuth (Google/Microsoft) and SAML strategies (abstract base
+ explicit `validate`); minor typings.
>   - Enable `DevtoolsModule` in development.
> - **GraphQL**:
> - Add `buildSchemaOptions.orphanedTypes` for client-config types; keep
Yoga/Sentry setup.
> - **Build/Runtime & Config**:
> - Standardize dist layout (remove `src` in paths): update scripts,
Docker `CMD`, Nx `project.json`, render scripts, TypeORM migration
paths, asset resolution.
> - Adjust `nest-cli.json` (watchOptions, asset globs, migrations
outDir, monorepo/root).
> - Improve config module imports (spread conditional); tsconfig
excludes `node_modules`.
>   - Minor Nx default: `start` target caching disabled.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1139fd85a97d0c72314d416d07464cc3c9942783. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-11-16 18:20:06 +01:00

129 lines
4.6 KiB
TypeScript

import {
type DynamicModule,
type MiddlewareConsumer,
Module,
RequestMethod,
} from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { GraphQLModule } from '@nestjs/graphql';
import { ServeStaticModule } from '@nestjs/serve-static';
import { existsSync } from 'fs';
import { join } from 'path';
import { YogaDriver, type YogaDriverConfig } from '@graphql-yoga/nestjs';
import { SentryModule } from '@sentry/nestjs/setup';
import { CoreGraphQLApiModule } from 'src/engine/api/graphql/core-graphql-api.module';
import { GraphQLConfigModule } from 'src/engine/api/graphql/graphql-config/graphql-config.module';
import { GraphQLConfigService } from 'src/engine/api/graphql/graphql-config/graphql-config.service';
import { MetadataGraphQLApiModule } from 'src/engine/api/graphql/metadata-graphql-api.module';
import { McpModule } from 'src/engine/api/mcp/mcp.module';
import { RestApiModule } from 'src/engine/api/rest/rest-api.module';
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
import { DataloaderModule } from 'src/engine/dataloaders/dataloader.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
import { GraphQLHydrateRequestFromTokenMiddleware } from 'src/engine/middlewares/graphql-hydrate-request-from-token.middleware';
import { MiddlewareModule } from 'src/engine/middlewares/middleware.module';
import { RestCoreMiddleware } from 'src/engine/middlewares/rest-core.middleware';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { ModulesModule } from 'src/modules/modules.module';
import { ClickHouseModule } from './database/clickHouse/clickHouse.module';
import { CoreEngineModule } from './engine/core-modules/core-engine.module';
import { I18nModule } from './engine/core-modules/i18n/i18n.module';
// TODO: Remove this middleware when all the rest endpoints are migrated to TwentyORM
const MIGRATED_REST_METHODS = [
RequestMethod.DELETE,
RequestMethod.POST,
RequestMethod.PATCH,
RequestMethod.PUT,
RequestMethod.GET,
];
@Module({
imports: [
SentryModule.forRoot(),
ConfigModule.forRoot({
isGlobal: true,
envFilePath: process.env.NODE_ENV === 'test' ? '.env.test' : '.env',
}),
GraphQLModule.forRootAsync<YogaDriverConfig>({
driver: YogaDriver,
imports: [GraphQLConfigModule, MetricsModule, DataloaderModule],
useClass: GraphQLConfigService,
}),
TwentyORMModule,
ClickHouseModule,
// Core engine module, contains all the core modules
CoreEngineModule,
// Modules module, contains all business logic modules
ModulesModule,
// Needed for the user workspace middleware
WorkspaceCacheStorageModule,
// Api modules
CoreGraphQLApiModule,
MetadataGraphQLApiModule,
RestApiModule,
McpModule,
DataSourceModule,
MiddlewareModule,
WorkspaceMetadataCacheModule,
// I18n module for translations
I18nModule,
// Conditional modules
...AppModule.getConditionalModules(),
],
})
export class AppModule {
private static getConditionalModules(): DynamicModule[] {
const modules: DynamicModule[] = [];
const frontPath = join(__dirname, '..', 'front');
// NestJS DevTools - can be useful for debugging and profiling
/* if (process.env.NODE_ENV === NodeEnvironment.DEVELOPMENT) {
modules.push(
DevtoolsModule.register({
http: true,
}),
);
} */
if (existsSync(frontPath)) {
modules.push(
ServeStaticModule.forRoot({
rootPath: frontPath,
}),
);
}
// Messaque Queue explorer only for sync driver
// Maybe we don't need to conditionaly register the explorer, because we're creating a jobs module
// that will expose classes that are only used in the queue worker
/*
if (process.env.MESSAGE_QUEUE_TYPE === MessageQueueDriverType.Sync) {
modules.push(MessageQueueModule.registerExplorer());
}
*/
return modules;
}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(GraphQLHydrateRequestFromTokenMiddleware)
.forRoutes({ path: 'graphql', method: RequestMethod.ALL });
consumer
.apply(GraphQLHydrateRequestFromTokenMiddleware)
.forRoutes({ path: 'metadata', method: RequestMethod.ALL });
for (const method of MIGRATED_REST_METHODS) {
consumer.apply(RestCoreMiddleware).forRoutes({ path: 'rest/*', method });
}
}
}