feat: add grpc env validator

This commit is contained in:
Sergey Krylov 2026-01-28 05:31:51 +03:00
parent 5a301d38ff
commit 15e63f9725
15 changed files with 114 additions and 4 deletions

View File

@ -34,6 +34,8 @@
"@prisma/client": "^7.3.0",
"@teacinema/common": "^1.0.0",
"@teacinema/contracts": "^1.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"dotenv-expand": "^12.0.3",
"ioredis": "^5.9.2",
"prisma": "^7.3.0",

View File

@ -1,14 +1,15 @@
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { grpcEnv } from './config'
import { PrismaModule } from './infra/prisma/prisma.module'
import { RedisModule } from './infra/redis/redis.module'
import { AuthModule } from './modules/auth/auth.module'
import { OtpModule } from './modules/otp/otp.module';
import { OtpModule } from './modules/otp/otp.module'
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
ConfigModule.forRoot({ isGlobal: true, load: [grpcEnv] }),
PrismaModule,
RedisModule,
AuthModule,

15
src/config/env/grpc.env.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import { registerAs } from '@nestjs/config'
import { validateEnv } from '@/shared/utils'
import type { GrpcConfig } from '../interfaces/grpc.interface'
import { GrpcValidator } from '../validators'
export const grpcEnv = registerAs<GrpcConfig>('grpc', () => {
validateEnv(process.env, GrpcValidator)
return {
host: process.env.GRPC_HOST,
port: parseInt(process.env.GRPC_PORT)
}
})

1
src/config/env/index.ts vendored Normal file
View File

@ -0,0 +1 @@
export * from './grpc.env'

2
src/config/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './env'
export * from './interfaces'

View File

@ -0,0 +1,5 @@
import { GrpcConfig } from './grpc.interface'
export interface AllConfigs {
grpc: GrpcConfig
}

View File

@ -0,0 +1,4 @@
export interface GrpcConfig {
host: string
port: number
}

View File

@ -0,0 +1 @@
export * from './all-configs.interface'

View File

@ -0,0 +1,9 @@
import { IsInt, IsString } from 'class-validator'
export class GrpcValidator {
@IsString()
public GRPC_HOST: string
@IsInt()
public GRPC_PORT: number
}

View File

@ -0,0 +1 @@
export * from './grpc.validator'

View File

@ -1,15 +1,22 @@
import { ConfigService } from '@nestjs/config'
import { NestFactory } from '@nestjs/core'
import { MicroserviceOptions, Transport } from '@nestjs/microservices'
import { AppModule } from './app.module'
import { AllConfigs } from './config'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
const config = app.get(ConfigService<AllConfigs>)
const host = config.get('grpc.host', { infer: true })
const port = config.get('grpc.port', { infer: true })
const url = `${host}:${port}`
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.GRPC,
options: {
url: 'localhost:50051',
url,
package: 'auth.v1',
protoPath: 'node_modules/@teacinema/contracts/proto/auth.proto',
loader: {

32
src/shared/utils/env.ts Normal file
View File

@ -0,0 +1,32 @@
import { ClassConstructor, plainToClass } from 'class-transformer'
import { validateSync } from 'class-validator'
export function validateEnv<T extends object>(
config: Record<string, unknown>,
envVariableClass: ClassConstructor<T>
) {
const validatedConfig = plainToClass(envVariableClass, config, {
enableImplicitConversion: true
})
const errors = validateSync(validatedConfig, {
skipMissingProperties: false
})
if (errors.length > 0) {
const errorMessages = errors
.map(
error =>
`\nError in ${error.property}\n:
+ ${Object.entries(error.constraints)
.map(([key, value]) => `${key}: ${value}`)
.join('\n')}`
)
.join('\n')
console.error(`\n${errors.toString()}`)
throw new Error(errorMessages)
}
return validatedConfig
}

View File

@ -0,0 +1 @@
export * from './env'

View File

@ -16,7 +16,7 @@
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"strictNullChecks": false,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,

View File

@ -1632,6 +1632,11 @@
"@types/methods" "^1.1.4"
"@types/superagent" "^8.1.0"
"@types/validator@^13.15.3":
version "13.15.10"
resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.15.10.tgz#742b77ec34d58554b94a76a14cef30d59e3c16b9"
integrity sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==
"@types/yargs-parser@*":
version "21.0.3"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@ -2437,6 +2442,20 @@ cjs-module-lexer@^2.1.0:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz#b3ca5101843389259ade7d88c77bd06ce55849ca"
integrity sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==
class-transformer@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336"
integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==
class-validator@^0.14.3:
version "0.14.3"
resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.3.tgz#834a4caafa8359aed73d7708badb4cf271be50fe"
integrity sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==
dependencies:
"@types/validator" "^13.15.3"
libphonenumber-js "^1.11.1"
validator "^13.15.20"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@ -4134,6 +4153,11 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
libphonenumber-js@^1.11.1:
version "1.12.35"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.12.35.tgz#149d4a66b2a7a518998c430c6d33465abaed23c4"
integrity sha512-T/Cz6iLcsZdb5jDncDcUNhSAJ0VlSC9TnsqtBNdpkaAmy24/R1RhErtNWVWBrcUZKs9hSgaVsBkc7HxYnazIfw==
lilconfig@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
@ -5740,6 +5764,11 @@ valibot@1.2.0:
resolved "https://registry.yarnpkg.com/valibot/-/valibot-1.2.0.tgz#8fc720d9e4082ba16e30a914064a39619b2f1d6f"
integrity sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==
validator@^13.15.20:
version "13.15.26"
resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.26.tgz#36c3deeab30e97806a658728a155c66fcaa5b944"
integrity sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==
vary@^1, vary@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"