我在NestJS应用程序中使用TypeORM。我的app.module.ts
具有非常标准的设置并可以运行:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService } from './config/config.service';
import { ConfigModule } from './config/config.module';
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
// @ts-ignore issues with the type of the database
useFactory: async (configService: ConfigService) => ({
type: configService.getDBType(),
host: configService.getDBHost(),
port: configService.getDBPort(),
username: configService.getDBUser(),
password: configService.getDBPassword(),
database: configService.getDBName(),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
inject: [ConfigService],
}),
ConfigModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
这是东西。如果要在CLI上运行迁移,则需要安装ormconfig.js
。我不想在ormconfig.js
和我的config.service.js
中都复制凭据。我创建了一个.env
文件,如下所示:
TYPEORM_CONNECTION = mysql
TYPEORM_HOST = app-db
TYPEORM_USERNAME = user
TYPEORM_PASSWORD = password
TYPEORM_DATABASE = db-dev
TYPEORM_PORT = 3306
TYPEORM_SYNCHRONIZE = true
TYPEORM_LOGGING = true
TYPEORM_ENTITIES = src/**/*.ts
TYPEORM_MIGRATIONS = src/migrations/**/*.ts
TYPEORM_MIGRATIONS_TABLE_NAME = migrations
由于env vars的定义如下所示:TypeORM Documentation,因此我继续进行重构,将app.module.ts
重构如下:
@Module({
imports: [TypeOrmModule.forRoot(), ConfigModule],
controllers: [],
providers: [],
})
export class AppModule {}
现在,当我使用DATABASE_HOST
时,遇到env变量DATABASE_PORT
,typeorm cli
等缺失的错误。
这是我的config.service.ts
import * as dotenv from 'dotenv';
import * as Joi from '@hapi/joi';
import * as fs from 'fs';
import { Injectable } from '@nestjs/common';
import { keys, pick } from 'lodash';
export type EnvConfig = Record<string, string>;
@Injectable()
export class ConfigService {
private readonly envConfig: Record<string, string>;
constructor(filePath: string) {
const envNames = keys(this.getJoiObject());
const envFromProcess = pick(process.env, envNames);
const envFromFile = fs.existsSync(filePath) ? dotenv.parse(fs.readFileSync(filePath)) : {};
const envConfig = Object.assign(envFromFile, envFromProcess);
this.envConfig = this.validateInput(envConfig);
}
private validateInput(envConfig: EnvConfig): EnvConfig {
const envVarsSchema: Joi.ObjectSchema = Joi.object(this.getJoiObject());
const { error, value: validatedEnvConfig } = envVarsSchema.validate(envConfig);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
return validatedEnvConfig;
}
private getJoiObject(): object {
return {
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_TYPE: Joi.string()
.valid('mysql')
.default('mysql'),
DATABASE_HOST: Joi.string().required(),
DATABASE_PORT: Joi.number().required(),
DATABASE_NAME: Joi.string().required(),
DATABASE_USER: Joi.string().required(),
DATABASE_PASSWORD: Joi.string().required(),
};
}
get(key: string): string {
return this.envConfig[key];
}
getPort(): number {
return parseInt(this.envConfig.PORT, 10);
}
getDBType(): string {
return this.envConfig.DATABASE_TYPE;
}
getDBHost(): string {
return this.envConfig.DATABASE_HOST;
}
getDBPort(): number {
return parseInt(this.envConfig.DATABASE_PORT, 10);
}
getDBName(): string {
return this.envConfig.DATABASE_NAME;
}
getDBUser(): string {
return this.envConfig.DATABASE_USER;
}
getDBPassword(): string {
return this.envConfig.DATABASE_PASSWORD;
}
}
这里的TYPEORM_
env var是否互斥?为了让TypeORM在CLI和NestJS应用程序的上下文中起作用,我们是否真的需要将环境变量复制为它们的DATABASE_
形式?这似乎是非常错误的。使TypeORM在CLI(我想在开发中进行迁移)和NestJS应用程序中工作而又不必重复这些变量的正确方法是什么?
答案 0 :(得分:7)
此解决方案使您可以在CLI使用和应用程序使用中使用相同的参数,而不会遇到代码重复。
使用path.join():
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { join } from 'path';
// tslint:disable-next-line: no-var-requires
require('dotenv').config();
class ConfigService {
constructor(private env: { [k: string]: string | undefined }) {}
//...etc
public getTypeOrmConfig(): TypeOrmModuleOptions {
return {
// obviously, change these if you're using a different DB
type: 'postgres',
host: this.getValue('POSTGRES_HOST'),
port: Number(this.getValue('POSTGRES_PORT')),
username: this.getValue('POSTGRES_USER'),
password: this.getValue('POSTGRES_PASSWORD'),
database: this.getValue('POSTGRES_DB'),
entities: [join(__dirname, '**', '*.entity.{ts,js}')],
migrationsTableName: 'migration',
migrations: [join(__dirname, '..', 'migrations', '*.ts')],
cli: {
migrationsDir: '../migrations',
},
synchronize: true,
ssl: this.isProduction(),
};
}
}
const configService = new ConfigService(process.env);
export default configService;
如果使用不带参数的TypeOrmModule.forRoot()
,则默认情况下,它将在项目的根目录下查找ormconfig.json
文件。您也可以为其提供TypeOrmModuleOptions参数,这是我建议的。我建议这样做就像Riajul Islam和Muhammad Zeeshan所做的那样:
@Module({
imports: [
TypeOrmModule.forRoot(configService.getTypeOrmConfig()),
// add other modules here as well
]
})
export class AppModule {}
这是一个简单的脚本,可让您生成ormconfig.json文件,该文件可用于CLI操作。
import configService from '../src/config.service';
import fs = require('fs');
fs.writeFileSync(
'ormconfig.json',
JSON.stringify(configService.getTypeOrmConfig(), null, 2), // last parameter can be changed based on how you want the file indented
);
您可能希望根据自己的文件结构以及实体的命名方式来更改entities
和migrations
属性的精确连接语句。
我的项目结构是:
.env // ALL environmental variables are stored here, both for Node and for other processes such as Docker
src
| config.service.ts
| app.module.ts // calls configService.getTypeOrmConfig()
| main.ts
scripts // for CLI only operations
| seed.ts // calls configService.getTypeOrmConfig() when creating a ConnectionOptions object for the database
| write-type-orm-config.ts // calls configService.getTypeOrmConfig() to create an ormconfig.json file at the root, which I use for some NPM scripts
migrations
| DB migrations go here...
这可能是您需要ormconfig.json文件的地方。
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"start": "nest start",
"start:dev": "nest start --watch",
"start:dev:db:seed": "ts-node -r tsconfig-paths/register scripts/seed.ts",
"start:debug": "nest start --debug --watch",
"start:dev:autoconfig": "yarn run typeorm:migration:run && yarn run start:dev:db:seed",
"start:prod": "node dist/src/main",
"pretypeorm": "(rm ormconfig.json || :) && ts-node -r tsconfig-paths/register scripts/write-type-orm-config.ts",
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
"typeorm:migration:generate": "yarn run typeorm -- migration:generate -n",
"typeorm:migration:run": "yarn run typeorm -- migration:run"
},
请注意,在生成迁移时,您必须指定迁移名称:yarn run typeorm:migration:generate ${MIGRATION_NAME}
https://medium.com/better-programming/typeorm-migrations-explained-fdb4f27cb1b3(有关使用NestJS配置TypeORM环境的好文章)
https://github.com/GauSim/nestjs-typeorm(上面的Git存储库)
答案 1 :(得分:1)
我发现您的代码没有任何问题,可能是我做不到,我也遇到了同样的问题,我可以为您提供代码示例,希望对您有所帮助。
.env
:APP_PORT=
TYPEORM_CONNECTION = <mysql | mongodb | pg>
TYPEORM_HOST =
TYPEORM_USERNAME =
TYPEORM_PASSWORD =
TYPEORM_DATABASE =
TYPEORM_PORT =
TYPEORM_SYNCHRONIZE = <true | false>
TYPEORM_LOGGING = <true | false>
TYPEORM_ENTITIES=**/*.entities.ts,src/**/*.entities.ts,src/**/*.entity.ts
TYPEORM_MIGRATIONS=database/migration/*.ts
TYPEORM_MIGRATIONS_DIR=database/migration
config.service.ts
import {TypeOrmModuleOptions} from '@nestjs/typeorm';
// tslint:disable-next-line: no-var-requires
require('dotenv').config();
class ConfigService {
constructor(private env: {[key: string]: string | undefined}) {}
private getValue(key: string, throwOnMissing = true): string {
const value = this.env[key];
if (!value && throwOnMissing) {
throw new Error(`config error - missing env.${key}`);
}
return value;
}
public ensureValues(keys: string[]) {
keys.forEach(key => this.getValue(key, true));
return this;
}
public getTypeOrmConfig(): TypeOrmModuleOptions {
return {
type: 'mysql',
keepConnectionAlive: true,
host: process.env.TYPEORM_HOST,
port: parseInt(process.env.TYPEORM_PORT) || 3306,
database: process.env.TYPEORM_DATABASE,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
entities: [__dirname + '/../**/*.entities{.ts,.js}']
};
}
}
const configService = new ConfigService(process.env).ensureValues([
'TYPEORM_DATABASE',
'TYPEORM_USERNAME',
'TYPEORM_PASSWORD'
]);
export {configService};
app.module.ts
@Module({
imports: [
TypeOrmModule.forRoot(configService.getTypeOrmConfig()),
]
})
export class AppModule {}
请让我知道此解决方案是否有效
答案 2 :(得分:0)
FinallyStatic答案的改进(不是真的,只是使用NestJs配置文档)。我认为这样更干净。
db-config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { registerAs } from "@nestjs/config";
import { config as setConfig } from 'dotenv';
setConfig();
setConfig({ path: '.dev.env' }); // use this if you use another .env file. Take the two setConfig if you use .env + other.env
export default registerAs('typeOrmConfig', (): TypeOrmModuleOptions => ({
type: 'mysql',
host: process.env.MYSQL_HOST || 'localhost',
port: Number(process.env.MYSQL_PORT) || 3306,
username: process.env.MYSQL_USER || 'test',
password: process.env.MYSQL_PASSWORD || 'test',
database: process.env.MYSQL_DATABASE || 'test',
entities: ['dist/**/*.entity{.ts,.js}'],
charset: "utf8mb4_unicode_ci",
synchronize: false,
cli: {
migrationsDir: "src/migrations"
},
migrations: ["dist/migrations/**/*.js"],
}));
app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import dbConfig from './config/db-config';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '.dev.env',
load: [dbConfig]
}),
TypeOrmModule.forRoot(dbConfig()),
// etc...
],
// etc...
});
write-type-orm-config.ts
import * as fs from 'fs';
import dbConfig from './config/db-config';
try {
fs.unlinkSync('ormconfig.json');
}
catch { }
fs.writeFileSync(
'ormconfig.json',
JSON.stringify(dbConfig(), null, 4),
);
package.json
与FinalStatic答案仅差一线,因此它也与ts文件中的unlink
Windows兼容。
"pretypeorm": "ts-node -r tsconfig-paths/register src/write-type-orm-config.ts",
结构
|-- src/
| |-- config/
| | |-- db-config.ts
| |
| |-- migrations/
| | |-- *migration files*
| |
| |-- app.module.ts
| |-- write-type-orm-config.ts
|
|-- .env
|-- ormconfig.json
答案 3 :(得分:0)
这就是我设法修复它的方式。使用单个配置文件,我可以在应用程序 boostrap 或使用 TypeOrm 的 CLI 上运行迁移。 package.json
上唯一的 mod 是传递 typeorm 的配置文件。
src/config/ormconfig.ts
import parseBoolean from '@eturino/ts-parse-boolean';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import * as dotenv from 'dotenv';
import { join } from 'path';
dotenv.config();
export = [
{
//name: 'default',
type: 'mssql',
host: process.env.DEFAULT_DB_HOST,
username: process.env.DEFAULT_DB_USERNAME,
password: process.env.DEFAULT_DB_PASSWORD,
database: process.env.DEFAULT_DB_NAME,
options: {
instanceName: process.env.DEFAULT_DB_INSTANCE,
enableArithAbort: false,
},
logging: parseBoolean(process.env.DEFAULT_DB_LOGGING),
dropSchema: false,
synchronize: false,
migrationsRun: parseBoolean(process.env.DEFAULT_DB_RUN_MIGRATIONS),
migrations: [join(__dirname, '..', 'model/migration/*.{ts,js}')],
cli: {
migrationsDir: 'src/model/migration',
},
entities: [
join(__dirname, '..', 'model/entity/default/**/*.entity.{ts,js}'),
],
} as TypeOrmModuleOptions,
{
name: 'other',
type: 'mssql',
host: process.env.OTHER_DB_HOST,
username: process.env.OTHER_DB_USERNAME,
password: process.env.OTHER_DB_PASSWORD,
database: process.env.OTHER_DB_NAME,
options: {
instanceName: process.env.OTHER_DB_INSTANCE,
enableArithAbort: false,
},
logging: parseBoolean(process.env.OTHER_DB_LOGGING),
dropSchema: false,
synchronize: false,
migrationsRun: false,
entities: [],
} as TypeOrmModuleOptions,
];
src/app.module.ts
import configuration from '@config/configuration';
import validationSchema from '@config/validation';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoggerService } from '@shared/logger/logger.service';
import { UsersModule } from '@user/user.module';
import { AppController } from './app.controller';
import ormconfig = require('./config/ormconfig'); //path mapping doesn't work here
@Module({
imports: [
ConfigModule.forRoot({
cache: true,
isGlobal: true,
validationSchema: validationSchema,
load: [configuration],
}),
TypeOrmModule.forRoot(ormconfig[0]), //default
TypeOrmModule.forRoot(ormconfig[1]), //other db
LoggerService,
UsersModule,
],
controllers: [AppController],
})
export class AppModule {}
package.json
"scripts": {
...
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config ./src/config/ormconfig.ts",
"typeorm:migration:generate": "npm run typeorm -- migration:generate -n",
"typeorm:migration:run": "npm run typeorm -- migration:run"
},
项目结构
src/
├── app.controller.ts
├── app.module.ts
├── config
│ ├── configuration.ts
│ ├── ormconfig.ts
│ └── validation.ts
├── main.ts
├── model
│ ├── entity
│ ├── migration
│ └── repository
├── route
│ └── user
└── shared
└── logger
答案 4 :(得分:0)
我想出了一个只涉及两行代码的解决方案: ormconfig.ts
import { AppConfig } from "./app.config";
export default AppConfig.getTypeOrmConfig();
这是我所做的:我首先创建了一个 app.config.ts 文件,该文件将包含通过 AppConfig 类与配置相关的所有内容。
此文件还包含一个 EnvironmentVariables 类(用于(在 AppConfig 类的 validateConfig 函数中)执行.env 文件(检查缺失值)和值转换(例如,POSTGRES_DB_PORT 变量将被转换为数字)。
代码如下: app.config.ts
import { join } from "path";
import { plainToClass } from "class-transformer";
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { MongooseModuleOptions } from "@nestjs/mongoose";
import { IsNumber, IsIn, validateSync, IsString } from "class-validator";
const enviroments = ["development", "test", "production"] as const;
type Environment = typeof enviroments[number];
class EnvironmentVariables {
@IsIn(enviroments)
NODE_ENV: Environment;
@IsString()
POSTGRES_DB_HOST: string;
@IsNumber()
POSTGRES_DB_PORT: number;
@IsString()
POSTGRES_DB_NAME: string;
@IsString()
POSTGRES_DB_USERNAME: string;
@IsString()
POSTGRES_DB_PASSWORD: string;
@IsString()
MONGO_DB_URI: string;
}
export class AppConfig {
private static get env(): EnvironmentVariables {
return plainToClass(EnvironmentVariables, process.env, {
enableImplicitConversion: true,
});
}
public static getTypeOrmConfig(): TypeOrmModuleOptions {
return {
type: "postgres",
host: this.env.POSTGRES_DB_HOST,
port: this.env.POSTGRES_DB_PORT,
username: this.env.POSTGRES_DB_USERNAME,
password: this.env.POSTGRES_DB_PASSWORD,
database: this.env.POSTGRES_DB_NAME,
// the rest of your config for TypeORM
};
}
public static get getMongooseUri(): string {
return this.env.MONGO_DB_URI;
}
public static getMongooseConfig(): MongooseModuleOptions {
return { useFindAndModify: false };
}
public static validateConfig(config: Record<string, unknown>) {
const validatedConfig = plainToClass(EnvironmentVariables, config, {
enableImplicitConversion: true,
});
const errors = validateSync(validatedConfig, {
skipMissingProperties: false,
});
if (errors.length > 0) {
throw new Error(errors.toString());
}
return validatedConfig;
}
}
然后,在 AppModule 上,我需要做的就是导入 ConfigModule(它从 .env 文件加载环境变量并使用我们的 validateConfig 函数对其进行验证)并使用 AppConfig 设置 TypeORM 和 Mongoose 的配置班级:
app.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import { MongooseModule } from "@nestjs/mongoose";
import { AppConfig } from "./app.config";
import { AppService } from "./app.service";
import { AppController } from "./app.controller";
@Module({
imports: [
ConfigModule.forRoot({
validate: AppConfig.validateConfig,
}),
TypeOrmModule.forRoot(AppConfig.getTypeOrmConfig()),
MongooseModule.forRoot(
AppConfig.getMongooseUri,
AppConfig.getMongooseConfig(),
),
],
controllers: [AppController],
providers: [
AppService,
})
最后,对于 ormconfig 文件,就这么简单:
ormconfig.ts
import { AppConfig } from "./app.config";
export default AppConfig.getTypeOrmConfig();
这里还有我的项目结构,以备不时之需:
project/
src/
app.config.ts
app.controller.ts
app.module.ts
app.service.ts
main.ts
ormconfig.ts
config
.env
package.json
package.lock.json
这里是我添加到 package.json 以使用 typeorm-cli 的脚本:
"typeorm:cli": "ts-node ./node_modules/typeorm/cli.js --config src/ormconfig.ts",
"run-migrations": "npm run typeorm:cli -- migration:run",
"create-migration": "npm run typeorm:cli -- migration:create --name",
"make-migrations": "npm run typeorm:cli -- migration:generate --pretty --name"
答案 5 :(得分:0)
我的配置。
// src/config/db.config.ts
import {registerAs} from "@nestjs/config";
export default registerAs('database', () => {
return {
type: "postgres",
logging: true,
host: process.env.DB_MAIN_HOST,
port: parseInt(process.env.DB_MAIN_PORT),
username: process.env.DB_MAIN_USER,
password: process.env.DB_MAIN_PASSWORD,
database: process.env.DB_MAIN_DATABASE,
autoLoadEntities: true,
// synchronize: process.env.MODE === "dev",
entities: ["src/**/*.entity.ts"],
migrations: ['src/migrations/*{.ts,.js}'],
cli: {
migrationsDir: 'src/migrations'
},
}
})
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import {ConfigModule, ConfigService} from '@nestjs/config';
import {TypeOrmModule} from "@nestjs/typeorm";
import dbConfiguration from "./config/db.config";
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [dbConfiguration],
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({...configService.get('database')})
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
// ormconfig.ts
import {ConfigModule} from "@nestjs/config";
import dbConfiguration from "./src/config/db.config";
ConfigModule.forRoot({
isGlobal: true,
load: [dbConfiguration],
})
export default dbConfiguration()
需要 ts-node
//package.json
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
"typeorm:migration:generate": "npm run typeorm -- migration:generate -n",
"typeorm:migration:run": "npm run typeorm -- migration:run"