Nestjs 配置 通用转发服务器,node 作为中间层,转发真实后端服务
此次代码默认是已经通过 nestjs 生成器生成(或直接克隆)的项目,在此基础上生成的。
- 设置 .env.production
配置 .env.production
主要配置的就是
BACKEND_URL=swapi.dev
# .env.production
APP_PORT=3000
DATABASE_URL=mongodb://localhost:27017/order-db
JWT_SECRET=mySuperSecretKey
NODE_ENV=production
BACKEND_URL=https://swapi.dev
- 设置 proxy.middleware.ts
// proxy/proxy.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request, Response } from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
@Injectable()
export class ProxyMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
use(req: Request, res: Response, next: () => void) {
const proxyConfig = {
target: this.configService.get<string>('BACKEND_URL'), // 改为使用 BACKEND_URL
changeOrigin: true,
pathRewrite: { '^/api': '' }, // 添加一个默认的路径重写规则,
};
console.log('当前代理配置:', proxyConfig);
if (!proxyConfig.target) {
throw new Error('代理目标地址未配置,请检查环境变量设置');
}
const proxy = createProxyMiddleware(proxyConfig);
void proxy(req, res, next);
}
}
- 设置 app.config.ts
// src/config/app.config.ts
import { plainToInstance } from 'class-transformer';
import { IsNumber, IsString, validateSync } from 'class-validator';
class EnvironmentVariables {
@IsNumber()
APP_PORT: number;
@IsString()
NODE_ENV: string;
@IsString()
BACKEND_URL: string;
}
export function validateAppConfig(config: Record<string, unknown>) {
const validatedConfig = plainToInstance(EnvironmentVariables, config, {
enableImplicitConversion: true,
});
const errors = validateSync(validatedConfig, {
skipMissingProperties: false,
});
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
}
- package.json
{
"name": "my-nest-project",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write "src/**/*.ts" "test/**/*.ts"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint "{src,apps,libs,test}/**/*.ts" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"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"
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"http-proxy-middleware": "^3.0.5",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\.spec\.ts$",
"transform": {
"^.+\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
- tsconfig.json
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"resolvePackageJsonExports": true,
"esModuleInterop": true,
"isolatedModules": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
}
}
- tsconfig.build.json
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
- app.module.ts
// \src\app.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ProxyMiddleware } from '../../proxy/proxy.middleware';
// 只导入抽离后的配置模块
import { AppConfigModule } from '../../config/config.module';
@Module({
imports: [
AppConfigModule, // ← 一行搞定配置!
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 所有路由都经过代理中间件
consumer.apply(ProxyMiddleware).forRoutes('*');
}
}
- index.ts
export * from './app/app.module'; // ← 导出 AppModule
- main.ts
// \main.ts
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './modules';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 获取配置
const configService = app.get(ConfigService);
const portStr = configService.get<string>('APP_PORT') ?? '3000';
const port = parseInt(portStr, 10);
if (isNaN(port)) {
throw new Error('APP_PORT must be a valid number');
}
await app.listen(port);
}
bootstrap().catch(err => {
console.log(err);
});