Когда вы используете сторонние источники для разработки приложений, необходимо задействовать ключи SSH или учетные данные API. Это становится проблемой, когда этим занимается команда разработчиков. Таким образом, исходный код должен периодически передаваться в репозитории Git. Как только код помещен в репозиторий, любой может увидеть его с помощью сторонних ключей.
Очень известное и широко используемое решение этой проблемы — использование переменных среды. Это локальные переменные, содержащие некоторую полезную информацию, например ключи API, и доступные для приложения или проекта.
Инструмент, известный как dotenv , облегчает создание таких переменных и делает их доступными для приложения. Это простой в использовании инструмент, который можно добавить в ваш проект с помощью любого менеджера пакетов.
Вам также может понравиться:
NestJS: базовая платформа NodeJS .
Мы будем использовать Yarn в качестве менеджера пакетов. Для начала добавим пакет с помощью терминала:
Машинопись
1
yarn add dotenv
Поскольку мы используем NestJS, который основан на Typescript, нам нужно добавить @types
пакет, который действует как интерфейс между JavaScript и пакетом Typescript.
Машинопись
xxxxxxxxxx
1
yarn add @types/dotenv
Поскольку используемая база данных — Postgres, нам нужно установить необходимый драйвер для Postgres.
Машинопись
xxxxxxxxxx
1
yarn add pg
Теперь установите модуль TypeORM в свой проект nest.
Машинопись
xxxxxxxxxx
1
yarn add @nestjs/typeorm typeorm
Теперь создайте сущности TypeORM в папке вашего проекта. Для иллюстрации, мы будем создавать папки, дб внутри Src папки нашего следующего проекта, и внутри этой папки создайте папку объекты , а также создать файл машинопись , содержащую информацию о вашем TypeORM лице
Для простоты мы создадим файл пользовательского объекта. Также мы создадим поле « id », поле « name » и поле « email » для этой сущности.
Машинопись
xxxxxxxxxx
1
#src/db/entities/user.entity.ts
2
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
3
@Entity({name: 'UserTable'})
4
class UserEntity extends BaseEntity {
5
@PrimaryGeneratedColumn()
6
id: number;
7
@Column()
8
name: string;
9
@Column()
10
email: string;
11
}
12
export default UserEntity;
Обратите внимание , что этот объект получил название, UserTable
, который является необязательным, но в случае миграции, она становится несколько полезной. Мы скоро узнаем причину.
Теперь создайте файл миграции для этого объекта пользователя. Файл миграции можно создать с помощью интерфейса командной строки с помощью следующей команды:
Машинопись
xxxxxxxxxx
1
typeorm migration:create -n CreateUserTable
Это создаст файл миграции с отметкой времени в качестве подстроки в имени этого файла.
Здесь CreateUserTable
будет имя вашего файла миграции, созданного средой TypeORM. Теперь мы создадим папку с миграциями внутри папки db и поместим в нее файл миграции, если это еще не сделано.
Теперь создайте отдельный файл, который будет использоваться в качестве утилиты миграции для определения схемы базы данных. Таким образом, мы можем назвать этот файл какigrationUtil.ts
Внутри этого файлаigrationUtil.ts создайте функции для получения различных типов столбцов, а именно varchar, integer и т. Д. Мы будем создавать две функции для иллюстрации, а именно getIDColumn
и getVarCharColumn
.
Машинопись
xxxxxxxxxx
1
#src/util/migrationUtil.ts
2
import { TableColumnOptions } from 'typeorm/schema-builder/options/TableColumnOptions';
3
class MigrationUtil {
4
public static getIDColumn(): TableColumnOptions[] {
5
const columns: TableColumnOptions[] = [];
6
columns.push({
7
name: 'userId',
8
type: 'int',
9
isPrimary: true,
10
isNullable: false,
11
isGenerated: true,
12
generationStrategy: 'increment',
13
});
14
return columns;
15
}
16
public static getVarCharColumn({ name, length = '255', isPrimary = false, isNullable = false, isUnique = false, defaultValue = null }): TableColumnOptions {
17
return {
18
name,
19
length,
20
isPrimary,
21
isNullable,
22
isUnique,
23
default: `'${defaultValue}'`,
24
type: 'varchar',
25
};
26
}
27
}
28
export default MigrationUtil;
Вот TableColumnOptions
тип, предоставляемый TypeORM из коробки. Код для этого файла прост и понятен. Когда вызывается одна из этих функций, они создают отдельный столбец в вашей таблице сущностей.
Теперь вернемся к файлу миграции CreateUserTable ; файл должен выглядеть так:
Джава
xxxxxxxxxx
1
#src/db/migrations/1578306918674-CreateUserTable.ts
2
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
3
export class CreateUserTable1578306918674 implements MigrationInterface {
4
public async up(queryRunner: QueryRunner): Promise<any> {
5
}
6
public async down(queryRunner: QueryRunner): Promise<any>
7
}
8
}
Теперь добавьте таблицу к этому файлу миграции, используя наш файл утилиты миграции как:
Машинопись
xxxxxxxxxx
1
#src/db/migrations/1578306918674-CreateUserTable.ts
2
3
.
4
private static readonly table = new Table({
5
name: 'UserTable',
6
columns: [
7
MigrationUtil.getIDColumn(),
8
MigrationUtil.getVarCharColumn({name: 'name'}),
9
MigrationUtil.getVarCharColumn({name: 'email'}),
10
],
11
});
Обратите внимание, что имя этой таблицы дается так же, как и userEntity , чтобы улучшить отображение таблиц сущностей для разработчиков. Кроме того, завершите код для асинхронного up
и down
использования методов QueryRunner
.
Идея заключается в том , чтобы создать три столбца в таблице пользователей — userId
, name
и email
.
Таким образом, в итоге файл миграции будет выглядеть примерно так:
Машинопись
xxxxxxxxxx
1
#src/db/migrations/1578306918674-CreateUserTable.ts
2
3
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
4
import MigrationUtil from '../../util/migrationUtil';
5
6
export class CreateUserTable1578306918674 implements MigrationInterface {
7
8
private static readonly table = new Table({
9
name: 'UserTable',
10
columns: [
11
MigrationUtil.getIDColumn(),
12
MigrationUtil.getVarCharColumn({name: 'name'}),
13
MigrationUtil.getVarCharColumn({name: 'email'}),
14
],
15
});
16
17
public async up(queryRunner: QueryRunner): Promise<any> {
18
await queryRunner.createTable(CreateUserTable1578306918674.table);
19
}
20
21
public async down(queryRunner: QueryRunner): Promise<any> {
22
await queryRunner.dropTable(CreateUserTable1578306918674.table);
23
}
24
25
}
Теперь создайте файлы среды, содержащие переменные среды. Мы будем создавать два. файлы env , а именно, development.env и test.env .
Переменными среды для development.env будут:
Машинопись
xxxxxxxxxx
1
#env/development.env
2
3
TYPEORM_CONNECTION = postgres
4
TYPEORM_HOST = 127.0.0.1
5
TYPEORM_USERNAME = root
6
TYPEORM_PASSWORD = root
7
TYPEORM_DATABASE = dotenv
8
TYPEORM_PORT = 5432
9
TYPEORM_ENTITIES = db/entities/*.entity{.ts,.js}
10
TYPEORM_MIGRATIONS = db/migrations/*{.ts,.js}
11
TYPEORM_MIGRATIONS_RUN = src/db/migrations
12
TYPEORM_MIGRATIONS_DIR = src/db/migrations
13
HTTP_PORT = 3001
И переменная окружения для test.env будет:
Машинопись
xxxxxxxxxx
1
#env/test.env
2
3
TYPEORM_CONNECTION = postgres
4
TYPEORM_HOST = 127.0.0.1
5
TYPEORM_USERNAME = root
6
TYPEORM_PASSWORD = root
7
TYPEORM_DATABASE = dotenv-test
8
TYPEORM_PORT = 5432
9
TYPEORM_ENTITIES = db/entities/*.entity{.ts,.js}
10
TYPEORM_MIGRATIONS = db/migrations/*{.ts,.js}
11
TYPEORM_MIGRATIONS_RUN = src/db/migrations
12
TYPEORM_ENTITIES_DIR = src/db/entities
13
HTTP_PORT = 3001
Теперь создайте файл конфигурации TypeORM для настройки соединения.
Мы разместим этот файл в папке config в папке src проекта.
Машинопись
xxxxxxxxxx
1
#src/config/database.config.ts
2
3
import * as path from 'path';
4
5
const baseDir = path.join(__dirname, '../');
6
const entitiesPath = `${baseDir}${process.env.TYPEORM_ENTITIES}`;
7
const migrationPath = `${baseDir}${process.env.TYPEORM_MIGRATIONS}`;
8
9
export default {
10
type: process.env.TYPEORM_CONNECTION,
11
host: process.env.TYPEORM_HOST,
12
username: process.env.TYPEORM_USERNAME,
13
password: process.env.TYPEORM_PASSWORD,
14
database: process.env.TYPEORM_DATABASE,
15
port: Number.parseInt(process.env.TYPEORM_PORT, 10),
16
entities: [entitiesPath],
17
migrations: [migrationPath],
18
migrationsRun: process.env.TYPEORM_MIGRATIONS_RUN === 'true',
19
seeds: [`src/db/seeds/*.seed.ts`],
20
cli: {
21
migrationsDir: 'src/db/migrations',
22
entitiesDir: 'src/db/entities',
23
},
24
};
Здесь process.env будет содержать все наши переменные окружения. Обратите внимание, что среда будет указана нами во время выполнения команды, и, таким образом, любой из файлов development.env или test.env будет взят в качестве переменных среды, предоставляющих файл.
В этой же папке создайте другой файл конфигурации для dotenv , и мы назовем его dotenv-options.ts .
Машинопись
xxxxxxxxxx
1
#src/config/dotenv-options.ts
2
3
import * as path from 'path';
4
5
const env = process.env.NODE_ENV || 'development';
6
const p = path.join(process.cwd(), `env/${env}.env`);
7
console.log(`Loading environment from ${p}`);
8
const dotEnvOptions = {
9
path: p,
10
};
11
12
export { dotEnvOptions };
Код для этого файла довольно прост. Обратите внимание, что строка кода, содержащая console.log
вызов, сообщит нам, какая среда используется гнездом при выполнении команд, и тот же файл предоставляется в качестве опций dotenv под ним.
Теперь, чтобы успешно интегрировать dotenv с Nest, официальные документы Nest рекомендуют создать службу конфигурации вместе с модулем конфигурации.
Таким образом, создайте папку служб, а внутри этой папки — файл config.service.ts .
Машинопись
xxxxxxxxxx
1
#src/Services/config.service.ts
2
3
import * as dotenv from 'dotenv';
4
import * as fs from 'fs';
5
import * as Joi from '@hapi/joi';
6
import { Injectable } from '@nestjs/common';
7
import IEnvConfigInterface from '../interfaces/env-config.interface';
8
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
9
import * as path from 'path';
10
11
@Injectable()
12
class ConfigService {
13
private readonly envConfig: IEnvConfigInterface;
14
15
constructor(filePath: string) {
16
const config = dotenv.parse(fs.readFileSync(filePath));
17
this.envConfig = this.validateInput(config);
18
}
19
20
public getTypeORMConfig(): TypeOrmModuleOptions {
21
const baseDir = path.join(__dirname, '../');
22
const entitiesPath = `${baseDir}${this.envConfig.TYPEORM_ENTITIES}`;
23
const migrationPath = `${baseDir}${this.envConfig.TYPEORM_MIGRATIONS}`;
24
const type: any = this.envConfig.TYPEORM_CONNECTION;
25
return {
26
type,
27
host: this.envConfig.TYPEORM_HOST,
28
username: this.envConfig.TYPEORM_USERNAME,
29
password: this.envConfig.TYPEORM_PASSWORD,
30
database: this.envConfig.TYPEORM_DATABASE,
31
port: Number.parseInt(this.envConfig.TYPEORM_PORT, 10),
32
logging: false,
33
entities: [entitiesPath],
34
migrations: [migrationPath],
35
migrationsRun: this.envConfig.TYPEORM_MIGRATIONS_RUN === 'true',
36
cli: {
37
migrationsDir: 'src/db/migrations',
38
entitiesDir: 'src/db/entities',
39
},
40
};
41
}
42
43
/*
44
Ensures all needed variables are set, and returns the validated JavaScript object
45
including the applied default values.
46
*/
47
private validateInput(envConfig: IEnvConfigInterface): IEnvConfigInterface {
48
const envVarsSchema: Joi.ObjectSchema = Joi.object({
49
NODE_ENV: Joi.string()
50
.valid('development', 'test')
51
.default('development'),
52
HTTP_PORT: Joi.number().required(),
53
}).unknown(true);
54
55
const { error, value: validatedEnvConfig } = envVarsSchema.validate(
56
envConfig,
57
);
58
if (error) {
59
throw new Error(`Config validation error: ${error.message}`);
60
}
61
return validatedEnvConfig;
62
}
63
}
64
65
export default ConfigService;
Вот IEnvConfigInterface
интерфейс, предоставленный нами явно для улучшения понимания кода.
Машинопись
xxxxxxxxxx
1
export default interface IEnvConfigInterface {
2
[key: string]: string;
3
}
dotenv.parse
прочтет содержимое файла, содержащего переменные окружения, и станет доступным для использования. Он может принимать строку или буфер и преобразовывать его в объект пар ключ-значение.
Затем этот объект проверяется с использованием объекта схемы Joi, который представляет собой библиотеку, предоставляемую Hapi. В соответствии с этой схемой мы указали, что среда (будь то тестирование или разработка) будет выбрана в качестве NODE_ENV
ключа в командной строке.
Кроме того, если среда не указана, установите для среды значение «разработка». Таким образом, наша envConfig
переменная теперь инициализируется с этим проверенным объектом.
Теперь создайте configModule
и импортируйте его в модуль приложения.
Машинопись
xxxxxxxxxx
1
#src/modules/config.module.ts
2
3
import { Global, Module } from '@nestjs/common';
4
import ConfigService from './Services/config.service';
5
6
@Global()
7
@Module({
8
providers: [
9
{
10
provide: ConfigService,
11
useValue: new ConfigService(`env/${process.env.NODE_ENV || 'development'}.env`),
12
},
13
],
14
exports: [ConfigService],
15
})
16
export default class ConfigModule {
17
}
Вот. Служба конфигурации вводится в этот модуль. Но поскольку наша служба конфигурации ожидает аргумент через конструктор, мы будем использовать useValue
для предоставления этой службе аргумент, который по умолчанию является файлом development.env , если во время выполнения команд CLI явно не указана среда.
Теперь мы создадим еще один файл загрузчика , который будет загружать все конфигурации для базы данных и dotenv .
Мы создадим этот файл в папке cli в папке src нашего проекта и назовем его loader.ts .
Машинопись
xxxxxxxxxx
1
import * as dotenv from 'dotenv';
2
import { dotEnvOptions } from '../config/dotenv-options';
3
4
// Make sure dbConfig is imported only after dotenv.config
5
6
dotenv.config(dotEnvOptions);
7
import * as dbConfig from '../config/database.config';
8
9
module.exports = dbConfig.default;
Обратите внимание, что в коде есть комментарий для импорта dbConfig только после импорта конфигурации dotenv. Это потому, что конфигурация нашей базы данных будет зависеть от среды, используемой nest.
Теперь в нашем файле package.json в разделе «scripts» мы добавим две пары ключ-значение, которые будут нашей командой CLI для миграции.
Машинопись
xxxxxxxxxx
1
2
3
"migrate:all": "ts-node ./node_modules/typeorm/cli migration:run -f src/cli/loader.ts",
4
"migrate:undo": "ts-node ./node_modules/typeorm/cli migration:revert -f src/cli/loader.ts"
5
6
Обратите внимание, что эта команда будет непосредственно выполнять наш файл загрузчика.
Вот и все!
Мы наконец-то интегрировали дотенв с NestJS и TypeORM.
Чтобы проверить это, запустите сервер базы данных, а затем выполните одну за другой следующие команды консоли:
Машинопись
xxxxxxxxxx
1
NODE_ENV=development yarn migrate:all
2
NODE_ENV=test yarn migrate:all
Это будет утешать среду, используемую в настоящее время нами.