feat: add nestjs api and worker skeletons

This commit is contained in:
Bastian Wagner
2026-08-17 13:18:24 +02:00
parent c23bda56a3
commit a08eecd4e3
17 changed files with 124 additions and 32 deletions

View File

@@ -7,4 +7,4 @@ import { AppService } from './app.service';
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],
}) })
export class AppModule {} export class ApiModule {}

View File

@@ -0,0 +1,13 @@
import { NestFactory } from '@nestjs/core';
import { ApiModule } from './api.module';
export async function bootstrapApi(): Promise<void> {
const app = await NestFactory.create(ApiModule);
app.enableShutdownHooks();
app.setGlobalPrefix('api/v1');
await app.listen(3000, '0.0.0.0');
}
if (require.main === module) {
void bootstrapApi();
}

View File

@@ -2,14 +2,14 @@ import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common'; import { INestApplication } from '@nestjs/common';
import request from 'supertest'; import request from 'supertest';
import { App } from 'supertest/types'; import { App } from 'supertest/types';
import { AppModule } from './../src/app.module'; import { ApiModule } from './../src/api.module';
describe('AppController (e2e)', () => { describe('ApiModule (e2e)', () => {
let app: INestApplication<App>; let app: INestApplication<App>;
beforeEach(async () => { beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({ const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule], imports: [ApiModule],
}).compile(); }).compile();
app = moduleFixture.createNestApplication(); app = moduleFixture.createNestApplication();

View File

@@ -1,8 +1,8 @@
{ {
"moduleFileExtensions": ["js", "json", "ts"], "moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".", "rootDir": "../../..",
"testEnvironment": "node", "testEnvironment": "node",
"testRegex": ".e2e-spec.ts$", "testRegex": "apps/api/test/.*\\.e2e-spec\\.ts$",
"transform": { "transform": {
"^.+\\.(t|j)s$": "ts-jest" "^.+\\.(t|j)s$": "ts-jest"
} }

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": false,
"outDir": "../../dist/apps/api"
},
"exclude": ["node_modules", "dist", "test", "**/*.spec.ts"]
}

View File

@@ -0,0 +1,20 @@
import { INestApplicationContext, Type } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { WorkerModule } from './worker.module';
type ContextFactory = (
module: Type<unknown>,
) => Promise<INestApplicationContext>;
export async function bootstrapWorker(
createContext: ContextFactory = (module) =>
NestFactory.createApplicationContext(module),
): Promise<INestApplicationContext> {
const app = await createContext(WorkerModule);
app.enableShutdownHooks();
return app;
}
if (require.main === module) {
void bootstrapWorker();
}

View File

@@ -0,0 +1,16 @@
import { bootstrapWorker } from './main';
describe('bootstrapWorker', () => {
it('creates an application context without starting an HTTP listener', async () => {
const close = jest.fn().mockResolvedValue(undefined);
const enableShutdownHooks = jest.fn();
const appContext = { close, enableShutdownHooks };
const createContext = jest.fn().mockResolvedValue(appContext);
const app = await bootstrapWorker(createContext as never);
expect(createContext).toHaveBeenCalledTimes(1);
expect(enableShutdownHooks).toHaveBeenCalledTimes(1);
expect(app).toBe(appContext);
});
});

View File

@@ -0,0 +1,4 @@
import { Module } from '@nestjs/common';
@Module({})
export class WorkerModule {}

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": false,
"outDir": "../../dist/apps/worker"
},
"exclude": ["node_modules", "dist", "test", "**/*.spec.ts"]
}

View File

@@ -1,8 +1,30 @@
{ {
"$schema": "https://json.schemastore.org/nest-cli", "$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src", "monorepo": true,
"root": "apps/api",
"sourceRoot": "apps/api/src",
"compilerOptions": { "compilerOptions": {
"deleteOutDir": true "deleteOutDir": true
},
"projects": {
"api": {
"type": "application",
"root": "apps/api",
"entryFile": "main",
"sourceRoot": "apps/api/src",
"compilerOptions": {
"tsConfigPath": "apps/api/tsconfig.app.json"
}
},
"worker": {
"type": "application",
"root": "apps/worker",
"entryFile": "main",
"sourceRoot": "apps/worker/src",
"compilerOptions": {
"tsConfigPath": "apps/worker/tsconfig.app.json"
}
}
} }
} }

View File

@@ -6,18 +6,21 @@
"private": true, "private": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"scripts": { "scripts": {
"build": "nest build", "build": "pnpm run build:api && pnpm run build:worker",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "build:api": "nest build api",
"start": "nest start", "build:worker": "nest build worker",
"start:dev": "nest start --watch", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"",
"start:debug": "nest start --debug --watch", "start": "nest start api",
"start:prod": "node dist/main", "start:dev": "nest start api --watch",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "start:debug": "nest start api --debug --watch",
"test": "jest", "start:api": "node dist/apps/api/main.js",
"start:worker": "node dist/apps/worker/main.js",
"lint": "eslint \"{apps,libs}/**/*.ts\" --fix",
"test": "jest --runInBand",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./apps/api/test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
@@ -57,15 +60,21 @@
"json", "json",
"ts" "ts"
], ],
"rootDir": "src", "rootDir": ".",
"testRegex": ".*\\.spec\\.ts$", "testRegex": ".*\\.spec\\.ts$",
"testPathIgnorePatterns": ["<rootDir>/node_modules/", "<rootDir>/dist/"],
"transform": { "transform": {
"^.+\\.(t|j)s$": "ts-jest" "^.+\\.(t|j)s$": "ts-jest"
}, },
"moduleNameMapper": {
"^@travel/configuration$": "<rootDir>/libs/configuration/src",
"^@travel/infrastructure$": "<rootDir>/libs/infrastructure/src"
},
"collectCoverageFrom": [ "collectCoverageFrom": [
"**/*.(t|j)s" "apps/**/*.(t|j)s",
"libs/**/*.(t|j)s"
], ],
"coverageDirectory": "../coverage", "coverageDirectory": "coverage",
"testEnvironment": "node" "testEnvironment": "node"
} }
} }

View File

@@ -1,8 +0,0 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

View File

@@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@@ -20,6 +20,10 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noImplicitAny": false, "noImplicitAny": false,
"strictBindCallApply": false, "strictBindCallApply": false,
"noFallthroughCasesInSwitch": false "noFallthroughCasesInSwitch": false,
"paths": {
"@travel/configuration": ["libs/configuration/src"],
"@travel/infrastructure": ["libs/infrastructure/src"]
}
} }
} }