node通用转发代理服务器

32 阅读2分钟

Nestjs 配置 通用转发服务器,node 作为中间层,转发真实后端服务

此次代码默认是已经通过 nestjs 生成器生成(或直接克隆)的项目,在此基础上生成的。

  1. 设置 .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
  1. 设置 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);
	}
}
  1. 设置 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;
}
  1. 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"
  }
}
  1. 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
  }
}
  1. tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
  1. 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('*');
	}
}
  1. index.ts
export * from './app/app.module'; // ← 导出 AppModule
  1. 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);
});