custom config module validation with joi - nestjs

So I followed the guide on how to create a configuration for my Nest app
https://docs.nestjs.com/techniques/configuration
and due to the fact I have many configuration parts I wanted to split the parts into multiple configuration services. So my app.module.ts imports a custom config module
#Module({
imports: [CustomConfigModule]
})
export class AppModule {}
This custom config module (config.module.ts) bundles all the config services and loads the Nest config module
#Module({
imports: [ConfigModule.forRoot()],
providers: [ServerConfigService],
exports: [ServerConfigService],
})
export class CustomConfigModule {}
Lastly I have a simple config service server.config.service.ts which returns the port the application is running on
#Injectable()
export class ServerConfigService {
constructor(private readonly configService: ConfigService) {}
public get port(): number {
return this.configService.get<number>('SERVER_PORT');
}
}
I would like to validate those services on application startup. The docs explain how to setup a validationschema for the configuration module
https://docs.nestjs.com/techniques/configuration#schema-validation
How can I use that for my service validation when using a custom config module?
Do I have to call joi in each service constructor and validate the properties there?
Thanks in advance

I believe in your ConfigModule.forRoot() you can set the validation schema and tell Nest to run the validations on start up instead of having to add it to each custom config service. The docs show something like:
#Module({
imports: [
ConfigModule.forRoot({
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'provision')
.default('development'),
PORT: Joi.number().default(3000),
}),
validationOptions: {
allowUnknown: false,
abortEarly: true,
},
}),
],
})
export class AppModule {}
Which would run validations on NODE_ENV and PORT. You could of course extend it out to more validations overall. And then you could just have one ConfigModule that has smaller config services that split each segment up so all validations are run on startup and only what you need is available in each module's context.

Related

NestJS can't resolve dependency

I'm trying to setup a graphql nestjs clean architecture, I have my resolver call the service, service calls repository and repository calls orm methods. Am I missing a step?
But I get this error:
[Nest] 59764 - 03/10/2022, 16:36:13 ERROR [ExceptionHandler] Nest can't resolve dependencies of the AthleteRepository (?). Please make sure that the argument AthleteEntityRepository at index [0] is available in the GraphQLModule context.
Potential solutions:
- If AthleteEntityRepository is a provider, is it part of the current GraphQLModule?
- If AthleteEntityRepository is exported from a separate #Module, is that module imported within GraphQLModule?
#Module({
imports: [ /* the Module containing AthleteEntityRepository */ ]
})
Error: Nest can't resolve dependencies of the AthleteRepository (?). Please make sure that the argument AthleteEntityRepository at index [0] is available in the GraphQLModule context.
This is my module:
#Module({
imports: [
InfraestructureModule,
NestGraphqlModule.forRoot<MercuriusDriverConfig>({
driver: MercuriusDriver,
graphiql: true,
autoSchemaFile: join(
process.cwd(),
'src/infraestructure/graphql/schema.gql',
),
sortSchema: true,
}),
],
controllers: [AuthResolver, UserResolver],
providers: [
FirebaseService,
AthleteEntity,
AthleteRepository,
AthleteService,
],
})
export class GraphQLModule {}
Adding repository by comment request, it's mostly just wrappers around the typerom Repository.
import { Controller } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { AthleteEntity } from '../entity/athlete.entity';
#Controller('')
export class AthleteRepository {
constructor(
#InjectRepository(AthleteEntity)
private athleteRepository: Repository<AthleteEntity>,
) {}
async getAthleteByUserId(id: string): Promise<AthleteEntity> {
return this.athleteRepository.findOne({ where: { id } });
}
async getAthletes(): Promise<AthleteEntity[]> {
return this.athleteRepository.find();
}
}
As you use #InjectRepository(AtheleteEntity) you need to have TypeOrmModule.forFeature([AtheleteEntity]) used in the imports of the GraphQlModule.
Side note: Is that supposed to be #Controller() or #Injectable()? It doesn't look like a standard HTTP or RPC controller to me
change AthleteRepository to an #Injectable instead of #Controller.

ConfigService of nest cannot pull .env variable

I wanted to implement the ConfigService of Nestjs v8.1.1
I've place the .env file in the project's root (not src folder) and the file has this content:
HOST=http://localhost
PORT=8088
The app.Module.ts is enriched with the import:
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
cache: true,
envFilePath: '.env',
}),
...]
I've tried without the options and with these listed options.
When it is trying to get the value of the variable, it results in
var address = this.configService.get('HOST', 'http://localhost')
^
TypeError: Cannot read properties of undefined (reading 'get')
FYI the code is run in an extended class, which should not make a difference.
export default class KsqldbSignalsClient extends ClientProxy {
constructor(private configService: ConfigService) {
super()
}
async connect(): Promise<any> {
var address = this.configService.get('HOST', 'http://localhost')
...
Any hint (even confirmation that it works for you) is appreciated.
Decorate the KsqldbSignalsClient class with #Injectable().
Step 1:
Install the 2 packages below
https://www.npmjs.com/package/config
https://www.npmjs.com/package/dotenv
Step 2:
#Module({
imports: [
ConfigModule.forRoot({
host: process.env.HOST,
port: process.env.PORT,
envFilePath: '.env',
}),
...]
Step 3:
Then inside you KsqldbSignalsClient class or anywhere
simply get the value like,
var address = process.env.HOST;

How to create service with Repository exports?

I created service with several repository:
#Injectable()
export class DataService {
constructor(
#InjectRepository(ClassicPrices, 'connect')
private classicPrices: Repository<ClassicPrices>,
#InjectRepository(ModernPrices, 'connect')
private modernPrices: Repository<ModernPrices>,
) {}
public pricesRepository(server): Repository<ModernPrices | ClassicPrices> {
switch (server) {
case 'modern':
return this.modernPrices;
case 'classic':
return this.classicPrices;
}
}
}
Data module settings:
#Module({
imports: [
TypeOrmModule.forFeature([
ClassicPrices,
ModernPrices
], 'connect'),
],
providers: [DataService],
exports: [DataService]
})
export class DataModule {}
When I use it in another modules^ I have error "Nest can't resolve dependencies of the DataService (connect_ClassicPrices). Please make sure that the argument connect_ClassicPrices at index [2] is available in the DataModule context.
Potential solutions:
If connect_ClassicPrices is a provider, is it part of the current DataModule?
If connect_ClassicPrices is exported from a separate #Module, is that module imported within DataModule?
#Module({
imports: [ /* the Module containing connect_ClassicPrices */ ]
})
How to export repository ?
If you want to use a repository from DataModule in other modules you need to add TypeOrmModule to the exports array in DataModule:
exports: [DataService, TypeOrmModule]
Then, any module that imports DataModule can use the repositories registered with TypeOrmModule.forFeature. Generally it's a better practice to export just the services and keep the repositories scoped to their module because that makes your app more modular.

Nest can't resolve dependencies of RabbitMQService service

I am building a nestjs app where I want to create a rabbitmq
#Module({
imports: [
ClientsModule.register([
{
name: 'rabbitmq',
transport: Transport.RMQ,
options: {
urls: [
'amqp://guest:guest#rabbitmq',
],
queue: 'my_queue',
},
},
]),
],
controllers: [],
providers: [RabbitMQService],
exports: [RabbitMQService],
})
And service:
#Injectable()
export class RabbitMQService {
constructor(
#Inject('rabbitmq') private client: ClientProxy
) {}
}
The error I am getting is: Nest can't resolve dependencies of the RabbitMQService (?). Please make sure that the argument rabbitmq at index [0] is available in the RabbitMQService context.
As much I am aware, this should work, but nope. Could anyone help?
From the error, it looks like somewhere in your application you have RabbitMQService in an imports array where #Module() classes are supposed to go. Make sure that you keep providers and other #Injectables() to the providers array and keep #Module() and other DynamicModules to the imports array. Common error docs

I'm getting following "Nest can't resolve dependencies of the WorkspaceController..." error in nest.js

I get this error
Nest can't resolve dependencies of the WorkspaceController (?). Please make sure that the argument API_SERVICE at index [0] is available in the WorkspaceModule context.
I have this code
app.module.ts
#Module({
imports: [
ClientsModule.register([
{
name: 'API_SERVICE',
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379'
}
}
]),
WorkspaceModule
],
controllers: [AppController],
providers: [AppService]
})
workspace.module.ts
#Module({
imports: [],
controllers: [WorkspaceController]
})
export class WorkspaceModule {}
workspace.controller.ts
#Controller()
export class WorkspaceController{
constructor(#Inject('API_SERVICE') private client: ClientProxy) {}
#Get("default-languages")
getDefaultLanguages():Observable<string[]> {
return this.client.send<any>({cmd:'getDefaultLanguages'},{});
}
}
You register the ClientsModule inside of AppModule which means that controlelrs and providers in AppModule's scope (i.e. in its own providers and controllers arrays) have access to that provider (#Inject('API_SERVICE')), but once you leave to another module's scope (like WorkspaceModule), that provider is no longer available. If you need that microservice client in several different modules, I'd suggest making a wrapper module for it, that imports and exports the ClientsModule, otherwise, you just need to move the ClientsMOdule.register from AppModule to WorkspaceModule
Example Wrapper
#Module({
imports: [ClientsModule.register(clientsModuleOptions)],
exports: [ClientsModule],
})
export class WrapperClientsModule {}
It's called module re-exporting

Resources