how to setup morgan-boddy in nestjs - node.js

I want to setup morgan-boddy as a midldleware to log requests and responses.
So I've created a function like that:
export function RequestLogging(app) {
const logger = new Logger('Request');
app.use(
morganBody(app, {
stream: { // <--- error is here "Void function return value is used "
write: (message) => logger.log(message.replace('\n', '')),
},
}),
);
}
That I call on main.ts
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
useRequestLogging(app);
// ...
}
However it seems does not work. I've got an error 'Void function return value is used' on line stream: {
Any idea how to fix?
UPDATE:
I tried to go different path and actually just stick morganBody in to main.ts as per docs:
import bodyParser from 'body-parser';
import morganBody from 'morgan-body';
app.use(bodyParser.json());
// hook morganBody to express app
morganBody(app); <-- getting error here "TS2345: Argument of type 'INestApplication' is not assignable to parameter of type 'Application'."
I wish there was a proper documentation how to approach in nestjs.

This is a pretty interesting middleware. It ends up needing the express instance itself because it calls app.use(morgan) and app.response.send() under the hood. I would really look into some other solution instead of something that accesses the response in this way.
Either way: this set up works
import { Logger } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import * as morgan from 'morgan-body';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = app.get(Logger);
(morgan as any)(app.getHttpAdapter().getInstance(), {
stream: {
write: (message: string) => {
logger.log(message.replace('\n', ''));
return true;
},
},
});
await app.listen(3033);
}
bootstrap();
The types for the package are wrong as well, it's not a default export, but a named one, so import morgan from 'morgan-body' doesn't work as advertised (at least it didn't for me).
The return true is necessary because write expects a stream.writable() method, which has returns a boolean. You can just default this to true. Then you have to use app.getHttpAdapter().getInstance() so that you ensure you pass the express instance to the middleware. Again, wonky setup, but it works.

Related

How can I use fastify request logger in other classes without having to pass it as a parameter?

I'm new in nodejs, I'm using fastify and I want to be able to use the req.logger in all the classes functions of the flow, this because I have a the request-id on req.logger, the first solution that came to my mind is to pass as a parameter the logger through all the function/classes but I think that would make the code kind of dirty, here is an example of my code:
app.ts
import pino from 'pino';
import fastify from 'fastify';
declare module 'fastify' {
interface FastifyInstance {
// augment fastify instance with the config object types
config: Config;
}
}
function build() {
const app = fastify({
logger: pino({
name: process.env.NAME,
level: process.env.LOG_LEVEL,
}),
disableRequestLogging: true,
requestIdHeader: 'correlation-id',
requestIdLogLabel: 'correlationId',
});
// register plugins
app.register(apiRoutes, fastify => ({
getObjectUseCase: new GetObjectUseCase(
new TestClass()),
}));
return app;
}
export { build };
routes.ts
import { FastifyPluginCallback } from 'fastify';
import { StatusCodes } from 'http-status-codes';
export const apiRoutes: FastifyPluginCallback<RoutesOpts> = async (fastify, options, done) => {
const getObjectUseCase = options.getObjectUseCase;
fastify.get<object>('/v1/api/:id', async (req, reply) => {
const id = req.params.payoutId;
req.logger.info('This is a logger print'); // has the correlation id inside it while printing
const storedObject = await getObjectCase.execute(id);
reply.code(StatusCodes.OK).send(storedObject);
});
}
GetObjectUseCase.ts
export class GetObjectUseCase {
private anotherClass: TestClass;
constructor(anotherClass: TestClass) {
this. anotherClass = anotherClass;
}
async execute(id: string): Promise<StoredObject> {
// I want to use the logger here with have the correlation id on it without having to pass it as an argument on the method, how is it posible?
return this.anotherClass.getById(id);
// also needed to use it inside anotherClass.getById so I will need to pass the logger also in the method
}
}
Hope I have been clear.
Thanks!
This may not be the best or only way to do it, but this has worked for me in the past.
Typically I structure my projects with an app.ts that just instantiates my FastifyInstance and then exports the log from that created instance. This allows me to use the log where ever I want to.
It looks something like this.
app.ts
import fastify from 'fastify';
const app = fastify({ logger: true /* Your logging configuration */});
export default app;
export const logger = app.log; // Allows me to log where ever I want.
server.ts
import app from './app';
... // All your fastify configuration and other stuff.
app.listen({ ... });
Now I can use the logger outside of fastify stuff.
get-object-use-case.ts
import { logger } from './app'; // Import your fastify logger to use in this class.
export class GetObjectUseCase {
private anotherClass: TestClass;
constructor(anotherClass: TestClass) {
this. anotherClass = anotherClass;
}
async execute(id: string): Promise<StoredObject> {
logger.info({/* Whatever you want to log here. */}); // Now you can use the logger here.
return this.anotherClass.getById(id); // You can just import the logger into the TestClass file to get logging enabled there.
}
}
This even allows you to log before your FastifyInstance is started. Check out this codesandbox for a running example.

When to load .env variables in NodeJS app?

I am coding a simple NodeJS Express REST API, using TypeScript. I have some environment variables that I load with dotenv.
I access my .env variables at two different stages in my code: index.ts, which is my start file, and in a MyControllerClass.ts file. To access these variables, the code is process.env.MY_ENV_VAR. To load them for the application, the code is dotenv.config().
As my index.ts file seems to be the root of my program (I configure my app in it), I use dotenv.config() to load my .env file for the rest of the program. However, in my MyControllerClass.ts file, in the constructor, if I do console.log(process.env.MY_ENV_VAR), I get "undefined". I could workaround this by adding a dotenv.config() in my constructor (it works) but it's nonsense to me to have it here.
How do I use dotenv.config() once and for all in my program, in a readable manner (like in an appropriate .ts file)? and more generally: what is a NodeJS Express loading cycle?
Here is a sample of the file structure of my code
src
├── index.ts
├── Authentication
│ └── authentication.router.ts
│ └── authentication.controller.ts
Here is the code of index.js
/**
* Required External Modules
*/
import * as dotenv from "dotenv";
import express from "express";
import cors from "cors";
import helmet from "helmet";
import { authenticationRouter } from "./authentication/authentication.router"
dotenv.config();
/**
* App Variables
*/
if(!process.env.PORT) {
process.exit(1);
}
const PORT: number = parseInt(process.env.PORT as string, 10);
const app = express();
/**
* App Configuration
*/
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(authenticationRouter);
app.use("api/authenticate/", authenticationRouter);
/**
* Server Activation
*/
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
Here is the code of authentication.router.ts
import express, { Request, Response } from "express";
import { AuthenticatorController } from "./authentication.controller";
export const authenticationRouter = express.Router();
const authenticatorController = AuthenticatorController.getInstance();
authenticationRouter.post("/api/authenticate", async (req: Request, res: Response) => {
try {
if (await authenticatorController.authenticate(req.body.login, req.body.password)) {
res.send({"status": "ok"})
} else
res.send({"status": "Error"})
} catch (e) {
console.debug(e)
res.send({"status": "500"});
}
});
Here is the code of authentication.controller.ts
import { ClientSecretCredential } from "#azure/identity";
import { SecretClient } from "#azure/keyvault-secrets";
import { Authenticator } from "./api/Authenticator";
import * as dotenv from "dotenv";
dotenv.config();
export class AuthenticatorController implements Authenticator {
private static singleInstance: AuthenticatorController | null = null;
private azureSecretCredential= new ClientSecretCredential(
process.env.AZURE_TENANT_ID as string,
process.env.AZURE_CLIENT_ID as string,
process.env.AZURE_CLIENT_SECRET as string);
private azureSecretClient = new SecretClient(
process.env.KEY_VAULT_URL as string,
this.azureSecretCredential);
private constructor () {}
public static getInstance(): AuthenticatorController {
if (this.singleInstance === null) {
this.singleInstance = new AuthenticatorController();
}
return this.singleInstance;
}
public async authenticate(login: string, password: string): Promise<Boolean> {
let isAuthenticated = false;
try {
const secret = await this.azureSecretClient.getSecret(login)
if (secret.name === login) {
if (secret.value === password) {
isAuthenticated = true;
}
}
} catch (e) {
console.debug(e);
}
return isAuthenticated;
}
}
You only call dotenv.config() once:
As early as possible in your application, require and configure
dotenv.
require('dotenv').config()
Therefore index.ts seems to be correct, process.env should then hold your parsed values. Maybe you can use something like this to make sure, data is parsed correctly:
const result = dotenv.config();
if (result.error) {
throw result.error;
}
console.log(result.parsed);
Edit:
You can try the following. I changed your exports a bit, because there is no need for a singleton within your controller.
authentication.router.ts:
// Imports (no dotenv; no dotenv.config())
// [...]
// Import controller
import { authenticatorController } from "./authentication.controller";
export const authenticationRouter = express.Router();
// Adding routes
// [...]
authentication.controller.ts:
// Imports (no dotenv; no dotenv.config())
// [...]
class AuthenticatorController implements Authenticator {
// [...]
}
export const authenticatorController = new AuthenticatorController();
index.ts:
// Imports (dotenv)
// [...]
const { error, parsed } = dotenv.config();
if (error) {
throw error;
}
console.log(parsed);
// [...]
app.use("api/authenticate/", authenticationRouter);
// [...]

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.

What is the correct way to dynamically import a class inside of a Firebase Cloud function using typescript?

In a Firebase Cloud Function project...
I have the following typescript file at the root of my src directory right along side of my main index.ts file which imports one dependency and exports a class that includes 2 methods. This file is titled bcrypt.class.ts:
import * as bcrypt from 'bcryptjs';
export default class BcryptTool {
public static hashValue(value: string, rounds: number, callback: (error: Error, hash: string) => void) : void {
bcrypt.hash(value, rounds, (error:any, hash:any) => {
callback(error, hash);
});
}
public static compare(value: string, dbHash: string, callback: (error: string | null, match: boolean | null) => void) {
bcrypt.compare(value, dbHash, (err: Error, match: boolean) => {
if(match) {
callback(null, true);
} else {
callback('Invalid value match', null);
}
});
}
}
In my Firebase Cloud functions index.ts file I import this class and make a call to it's 'compare' method within one of my functions without issue, this works as expected:
'use strict';
const express = require('express');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });
const admin = require('firebase-admin');
admin.initializeApp();
const api = express();
import BcryptTool from './bcrypt.class'; // <-- i import the class here
// and use it in a function
api.use(cors);
api.post('/credentials', async (request: any, response: any) => {
BcryptTool.compare(...) // <--- calls to this method succeed without issue
});
The problem
My application includes many functions, but I only need the class noted above in one of them, so in an attempt to optimize cold start time for all my other functions, I attempt to dynamically import this class inside of the function that needs it instead of importing it into the global scope as outlined above. This does not work and I cannot figure out why:
'use strict';
const express = require('express');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });
const admin = require('firebase-admin');
admin.initializeApp();
const api = express();
api.use(cors);
api.post('/credentials', async (request: any, response: any) => {
const BcryptTool = await import('./bcrypt.class'); // <-- when i attempt to import here instead
BcryptTool.compare(...) // <--- subsequent calls to this method fail
// Additionally, VS Code hinting displays a warning: Property 'compare' does not exist on type 'typeof import('FULL/PATH/TO/MY/bcrypt.class')'
});
Is my class not written or exported correctly?
Am I not importing the class correctly inside of my cloud function?
The top-level import (import BcryptTool from './bcrypt.class';) will automatically import the default export from the bcrypt.class module. However, when using the import statement as a function (so called "dynamic import"), it will import the module itself, not the default export.
You can see the difference when you would console.log(BcryptTool) both imports:
import BcryptTool from './bcrypt.class' will show { default: { [Function: BcryptTool] hashValue: [Function], compare: [Function] } }
const BcryptTool = await require('bcrypt.class') will show { [Function: BcryptTool] hashValue: [Function], compare: [Function] }
Did you notice the default in the first console.log? That shows you imported the module, not the default.
Now actually the import BcryptTool from './bcrypt.class' syntax is syntactic sugar for doing import { default as BcryptTool } from './bcrypt.class'. If you apply this knowledge on the dynamic import, you could do this:
const BcryptToolModule = await import('./bcrypt.class');
BcryptToolModule.default.compare(...);
Or in a cleaner syntax:
const { default: BcryptTool } = await import('./bcrypt.class');
BcryptTool.compare(...);

Unit testing in Typescript with Dependency Injection

I'm running into problems with typescript during unit testing. I try to unit test an module like the following code example:
import {Express} from 'express'
export interface BootClassInterface {
setup(): Express
}
export class BootClass implements BootClassInterface {
constructor(private express: () => Express ){
}
public test(){
const app = this.express()
app.get("/test", (req, res) => {
res.send("Hello World")
})
return app;
}
}
For testing propose I want to know if express().get() was called and if the first parameter was '/test'. Before I switched to typescript I always used the module sinonJS to spy or stub the functionality so that I could test of a certain dependency was used correctly. Now with Typescript I run into problems with the strict types that I've set in the module. By example:
import * as chai from 'chai'
import 'mocha'
import * as sinon from 'sinon'
import * as express from 'express'
import * as Boot from '../../src/Core/Boot'
const assert = chai.assert
suite('[CORE] unit test /Core/Boot.ts', () => {
let BootClass: Boot.BootClassInterface
setup(() => {
const expressApp = () => {
const app = express()
app.get = sinon.spy()
return app
}
const Services: Boot.BootServicesInterface = { express: expressApp }
BootClass = new Boot.BootClass(Services)
})
test('Do first test', () => {
const app = BootClass.setup()
chai.assert.equal(app.get.called, 1)
})
})
The code example above results in the following Typescript compiling error:
error TS2339: Property 'called' does not exist on type 'ApplicationRequestHandler<Express>'.
I can see why typescript returns this error and somehow it is even obvious. I even know a possible solution where I accept Express as any in the module.
But I'm looking for a more elegant way to be able to spy/stub/mock my dependencies for testing propose, but at the same time have the advantages of strict typing.
AS you have stated you specify Express as the return type on BootClassInterface interface. Therefore app would be considered Express and it will look up its properties instead of your mock.
You can also just cast one property of app as any. Try:
chai.assert.equal((<any>app.get).called, 1)
Or using Sinon type definition:
chai.assert.equal((<SinonSpy>app.get).called, 1)

Resources