Right way to get ConfigService when bootstrap microservice - node.js

I would like to know if i'm getting ConfigService the right way during bootstrap my microservice.
Is there a way to do it using NestFactory.createMicroservice()?
async function bootstrap() {
const app = await NestFactory.create(CoreModule, {
logger: new MyLogger(),
});
const configService: ConfigService = app.get(ConfigService);
app.connectMicroservice({
transport: Transport.TCP,
options: {
port: configService.PORT,
},
});
await app.startAllMicroservicesAsync();
}

Yes there is a way to do it in the NestFactory, you're already doing it the right way! If you'd like another example, this is my bootstrap function:
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: new MyLogger()
});
const config = app.get<ConfigService>(ConfigService);
const port = config.get('PORT');
configure(app, config);
await app.listen(port);
scribe.info(
`Listening at http://localhost:${port}/${config.get('GLOBAL_PREFIX')}`
);
}
Where MyLogger is a custom implementation of the Nest default logger and configure(app, config) is a lot of application configuration done in a separate file to keep the bootstrap function lean and easy to maintain. Of course, the "listening at ..." also needs to be changed before heading to production, but this app is still in development for me.
The only thing I would suggest to you is changing
const configService: ConfigService = app.get(ConfigService);
to
const configService = app.get<ConfigService>(ConfigService);
using the generics for app.get() to give you the type information.

Related

Best practise for getting access to the app instance

I'm creating a NestJS app with Firebase cloud functions. I have to use both onRequest and onCreate (Firestore Events) from Firebase in the NestJS application. It's quite straightforward how to address the onRequest events. However, I'm not sure if I'm doing it right when having to do both at the same time. In order for me to pass the onCreate event changes and context to the service layer, I need to get access to the AppService class. To do that I need access to the app instance that has been created. However, I feel like I'm creating two instances of the app (refer to the code). I would like to know if my current my implementation is best to practice or if there is any way it can be improved. Please note I'm a frontend developer so this work is outside my comfort zone. I would like to know the best way to do this, especially if I have to work with more events such as onUpate, onDelete etc.
import { NestFactory } from '#nestjs/core';
import { ExpressAdapter } from '#nestjs/platform-express';
import { AppModule } from './app.module';
import * as express from 'express';
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { Express } from 'express-serve-static-core';
import { AppService } from './app.service';
const server = express();
export const createNestServer = async (expressInstance: Express) => {
//FIRST APP INSTANCE
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
admin.initializeApp();
return app.init();
};
createNestServer(server)
.then((v) => console.log('Nest Ready'))
.catch((err) => console.error('Nest broken', err));
//exporting all onRequest events
export const api = functions.https.onRequest(server);
//exporting the onUserCreate event
exports.onUserCreate = functions.firestore
.document('users/{docId}')
.onWrite(async (change, context) => {
console.log('Changes are tracked');
//SECOND APP INSTANCE
const app = await NestFactory.create(AppModule);
return app.get(AppService).onCreate(change, context);
});
I assume you have something like a UsersController (and others) that handles retrieving, creating, updating the users as part of the NestJS application. Just pass the Express instance (wrapped by the NestJS adapter) to the Cloud Function HTTPS handler (onRequest). NestJS will take care of setting up the routes.
export const api = functions.https.onRequest(server)
#Controller('users')
export class UsersController {
constructor(private readonly usersService: UserService) {}
#Get(':userId')
async getUser(#Param('userId') id: String) {
// retrieve user
}
#Post()
async create(#Body() createUserDto: CreateUserDto) {
// create a new user
}
...
}
#Module({
controllers: [UsersController],
providers: [AppService, UsersService],
exports: [AppService, UsersService]
})
export class AppModule {}
The AppService is a provider registered in the AppModule. You could extract it from the INestApplication instance.
const server = express();
let appService: AppService;
export const createNestServer = async (expressInstance: Express) => {
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
admin.initializeApp();
appService = app.get(AppService);
return app.init();
};
Then you could use it within the onWrite firestore trigger.
Seems like you are trying to do 2 differents tasks with the same NestJS app. Just let the NestJS (or ExpressJS) app handle the HTTPS requests. And for the onCreate, onWrite ... triggers implement a different solution. Perhaps don't rely on NestJS to handle these types of triggers.
NestJS personally feels like overkill, you could just use ExpressJS. You wouldn't have to register the AppService as a provider and jump through hoops.
The following approach has allowed me to solve the multiple instatiation issue of the app instance. Hopefully this is a good and acceptable way to do this. Feedback is more than welcome.
const server = express();
export const createNestServer = async (expressInstance: Express) => {
//FIRST APP INSTANCE
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
admin.initializeApp();
return app.init();
};
const main = createNestServer(server);
export const api = functions.https.onRequest(server);
exports.onUserWrite = functions.firestore
.document('users/{docId}')
.onWrite((change, context) =>
main.then((app) => {
return app.get(AppService).onCreate(change, context);
}),
);

Binding interceptor to NestJS microservice method

I just created a simple interceptor to override every error thrown by my application, just like the one on Nest's documentation:
#Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
catchError(err => throwError(() => new ApplicationException())),
);
}
}
And altough exceptions caused by http requests indeed are caught in that interceptor, I just can't make it work with RPC requests (like KafjaJS and events).
Just like the documentation, I've binded it on my app.module:
{
provide: APP_INTERCEPTOR,
useClass: ErrorsInterceptor,
}
I know I'm probably missing something out, can someone clarify where and why what I'm doing is not working and how to make it work?
#Edit: I forgot to mention that I made it work #UseInterceptors() above my controller's method, but I'd like to make it work without it.
#Edit 2: I have a hybrid appplication, this is what my main looks like (as asked by Jay):
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: WinstonModule.createLogger(winstonTransports),
});
app.connectMicroservice<MicroserviceOptions>(kafkaConfig);
const logger = app.get<winston.Logger>(WINSTON_MODULE_NEST_PROVIDER);
app.useLogger(logger);
app.enableCors(corsConfig);
await app.startAllMicroservices();
await app.listen(env.PORT);
}
When working with hybrid applications you need to add { inheritAppConfig: true } to the connectMicroservice() method as a second parameter as described in the docs. This means your main.ts should be
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
logger: WinstonModule.createLogger(winstonTransports),
});
app.connectMicroservice<MicroserviceOptions>(kafkaConfig, { inheritAppConfig: true });
const logger = app.get<winston.Logger>(WINSTON_MODULE_NEST_PROVIDER);
app.useLogger(logger);
app.enableCors(corsConfig);
await app.startAllMicroservices();
await app.listen(env.PORT);
}

Mocking a function in a jest environment file

I want to share a server between all my tests, to do this I create the file server-environment.js
const NodeEnvironment = require('jest-environment-node')
const supertest = require('supertest')
//A koa server
const { app, init } = require('../src/server')
class ServerEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context)
this.testPath = context.testPath
}
async setup() {
await init
this.server = app.listen()
this.api = supertest(this.server)
this.global.api = this.api
}
async teardown() {
this.server.close()
}
}
module.exports = ServerEnvironment
The thing is that I want to mock some middleware that the servers routes use but I can't really find a way to do that. If I try to declare jest.mock anywhere in the file I get the error that jest isn't defined. If I mock the function in the actual test file the global wouldn't make use of it. Not sure if something like this would be possible to do with Jest?
I had a same issue and solved it by adding setupFilesAfterEnv.
jest.config.js:
module.exports = {
...
setupFilesAfterEnv: [
'./test/setupMocks.js'
]
...
};
test/setupMocks.js
jest.mock('path/to/api', () => global.api);

Error: metatype is not a constructor when using instance of own HTTPS Server class

Good evening, I am playing around with nest and want to achieve an own HTTPS-Server that can be instantiated everywhere in other projects. Right at the beginning I get the following error-message:
TypeError: metatype is not a constructor
… when I init the following HTTPS-Server:
import { Injectable } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '#nestjs/platform-fastify';
import * as fs from 'fs';
#Injectable()
export class HttpsServer {
constructor() {}
async bootstrap() {
const httpsOptions = {
key: fs.readFileSync('./certs/server.key'),
cert: fs.readFileSync('./certs/server.cert'),
};
const app = await NestFactory.create<NestFastifyApplication>(
new FastifyAdapter({ https: httpsOptions }),
);
await app.listen(443);
}
}
like this:
import { Logger } from '#nestjs/common';
import { HttpsServer } from 'server-lib';
const logger = new Logger();
const app = new HttpsServer();
app.bootstrap().then(() => {
logger.log('Bootstrap complete!');
}).catch((error) => {
logger.log('Bootstrap failed: ', error);
process.exit(1);
});
Thx for help...
All Nest applications needs to have a "RootModule" of some sort. With nest new applications this is a AppModule. This class holds the metadata for the server for how to tie everything together and run. What you could probably do is modify your HttpModule's constructor to do something like
export class HttpsServer {
constructor(private readonly rootModule: Type<any>) {} // Type comes from #nestjs/common
async bootstrap() {
const httpsOptions = {
key: fs.readFileSync('./certs/server.key'),
cert: fs.readFileSync('./certs/server.cert'),
};
const app = await NestFactory.create<NestFastifyApplication>(
this.rootModule,
new FastifyAdapter({ https: httpsOptions }),
);
await app.listen(443);
}
}
So now when you call new HttpServer() you pass in the root module and have everything else already set up. The NestFactory will instantiate the metadata from there properly, and you'll use the FastifyAdapter.
For more information, I suggest you follow the docs overview to get a feeling of how these classes fit together and why they're needed.
You probably have used an incorrect guard.
Check #UseGuards() and use the correct guard for that function.
If you're importing some files from index in the same folder such as all entities as
import * as Entities from './entities
make sure that you have that ./entities in the folder path and just plan '.'
It does not throws error but while building this is not recognized. use the full file path and it will most probably solve the issue.

How to get Nestjs configService instance in main.ts before instantiating app

My app instance depends on configuration : serviceName in that case
const serviceName = 'authentication-service'
const servicePrefix = `api/${serviceName}`;
const swaggerPrefix = 'swagger';
...
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
requestIdLogLabel: serviceName,
logger: true,
...
}),
{
// logger: ['log']
logger: process.env.DEV === '1' ? ['log', 'debug', 'error', 'verbose', 'warn'] : ['error', 'warn'],
});
NestJs documentation uses app instance to get the configService singleton :
const configService = app.get(ConfigService);
const port = configService.get('PORT');
Is there any way to get the configService instance before instantiating my app?
Nope. You could call dotenv's config() method yourself and populate process.env if you really need to, but that's about it. Technically, if you really wannted you could create a NestFactory.createApplicationContext() call and get the ConfigService from there, close the context and start up the application with the standard NestFactory.create, but that will end up doing the DI resolution twice, which would be a pretty slow startup
I had a similar need (for the Mikro-ORM configuration file). What I did is to create a new instance of ConfigService:
import {ConfigService} from '#nestjs/config'
const configService = new ConfigService();
Then I could use configService to get the port number:
configService.get<number>('PSQL_PORT', 5432)
What you need to do is to create a dummy app instance before the actual app.
const configApp = await NestFactory.create(AppModule);
let configService = configApp.get(ConfigService);
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
requestIdLogLabel: serviceName,
logger: true,
...
}),
{
// logger: ['log']
logger: process.env.DEV === '1' ? ['log', 'debug', 'error', 'verbose', 'warn'] : ['error', 'warn'],
});
//...
I am using custom configuration files as defined in https://docs.nestjs.com/techniques/configuration#custom-configuration-files
and with that I can use just use the following to get access to the config already in a js object.
async function bootstrap() {
const conf = configuration();
...
I initiall tried the solution from tukusejssirs which also worked but it would not map the env entries to js object yet so I came up with this one.
Comments appreciated as it feels a bit weird...

Resources