web-dev-qa-db-fra.com

Configurer Typeorm avec une configuration pour l'application CLI et NESTJS

J'utilise Typeorm dans mon application Nestjs. Mon app.module.ts a une configuration et des travaux très standard:

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 {}

Voici la chose. Si je veux exécuter des migrations sur le CLI, j'ai besoin d'avoir un ormconfig.js. Je ne souhaite pas dupliquer les informations d'identification dans les deux ormconfig.js et dans mon config.service.js. J'ai créé un .env Fichier qui ressemble à ce qui suit:

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

Étant donné Env Vares sont désormais définis comme décrit ici: Typeorm Documentation , je suis allé de l'avant et j'ai refactored app.module.ts Pour ressembler à ce qui suit:

@Module({
  imports: [TypeOrmModule.forRoot(), ConfigModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

Maintenant, je reçois des erreurs envers Vars DATABASE_Host, DATABASE_PORT etc manque quand j'utilise typeorm cli.

Voici mon 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;
  }
}

Sont le TYPEORM_ env vaille mutuellement exclusivité ici? Avons-nous vraiment besoin de reproduire les variables d'environnement à leur DATABASE_ forme afin d'avoir des travaux de typeorm dans CLI et dans le contexte de l'application NestJS? Cela semble très faux. Quelle est la bonne façon d'avoir le travail de typeorm à la fois CLI (je le souhaite pour les migrations dans le développement) et dans l'application NestJS sans avoir à dupliquer ces variables?

6
randombits

Solution

Cette solution vous permet d'utiliser les mêmes paramètres pour l'utilisation de la CLI et l'utilisation des applications, sans entrer dans la duplication de code.

Utilisez le chemin.join ():

config.service.ts

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;

app.module.ts

Si vous utilisez TypeOrmModule.forRoot() sans arguments, cela permet de rechercher par défaut un fichier ormconfig.json à la racine de votre projet. Vous pouvez également le fournir avec un paramètre TypeormModuleOptions également, que je recommanderais. Je suggérerais de faire cela exactement comme le Riajul Islam et Muhammad Zeeshan ont fait:

@Module({
    imports: [
        TypeOrmModule.forRoot(configService.getTypeOrmConfig()),
        // add other modules here as well
    ]
})
export class AppModule {}

écriture-type-orm-config.ts

Ceci est un script simple qui vous permet de générer le fichier ormconfig.json qui est utile pour les opérations 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
);

Structure de projet

Vous voudrez probablement modifier les instructions exactes de jointure pour vos propriétés entities et migrations en fonction de votre propre structure de fichiers et de la manière dont vous nommez vos entités.

Ma structure de projet est:

.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...

Exemple de packages.json scripts

Ceci est probable où vous aurez besoin d'un fichier 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"
  },

Notez que vous devrez spécifier un nom de migration lors de la génération d'une migration: yarn run typeorm:migration:generate ${MIGRATION_NAME}

Les références

https://medium.com/better-programming/typeorm-migrations-explaine-fdb4f27cb1b (bon article sur la configuration d'un environnement de typeorm avec Nestjs)
[.____] https://github.com/gausim/nestjs-typeorm (référentiel git pour ce qui précède)

2
FinallyStatic

Amélioration de la réponse enfin (pas vraiment, simplement utiliser Nestjs Config Docs). Je pense que c'est plus propre de cette façon.

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...
});

type d'écriture-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),
);

paquet.json

Une différence de ligne à partir de la réponse enfin statique, il est donc également compatible Windows avec le fichier unlink dans le fichier TS.

"pretypeorm": "ts-node -r tsconfig-paths/register src/write-type-orm-config.ts",

structure

|-- src/
| |-- config/
| | |-- db-config.ts
| |
| |-- migrations/
| | |-- *migration files*
| |
| |-- app.module.ts
| |-- write-type-orm-config.ts
|
|-- .env
|-- ormconfig.json
0
Ripper346