Node Koa How do I pass a param into middleware? - node.js

Converting my Express app to Koa...
I'm googling and I'm googling, I can't find how to pass extra params into Koa middleware. For example...
router.post('/', compose([
Midware.verifyAuthToken,
Midware.bodySchemaTest(UserController.bodyAttribs),
Midware.injectionTest
]), UserController.postNew);
I need send some variable bodyAttribs (string array) names into the bodySchemaTest middleware and I don't know how to do this in Koa.
I'm just now trying Koa. Please share your expertise :-)

Ok I worked it out myself. Not sure if this is smart or the "right" way to do this but the solution for me was to create a piece of middleware in each controller that sets expected schema attribs in ctx.state.bodyAttribs.
Like this...
// /src/routers/user.router.ts
import * as UserController from '../controller/user.controller';
import * as Midware from './middleware';
import Router from 'koa-router';
import compose from 'koa-compose';
const router = new Router();
router.prefix('/user');
router.get('/', Midware.verifyAuthToken, UserController.getAll);
router.post('/', compose([
UserController.bodyAttribs,
Midware.verifyAuthToken,
Midware.bodySchemaTest,
Midware.injectionTest,
]), UserController.postNew);
module.exports = router;
// /src/controller/user.controller.ts
import { Context } from 'koa';
import { UserData } from '../data/mongo/user.data.mongo';
import { UserModel } from '../model/user.model';
import { EmailValidate } from '../service/email-validate.service';
import * as Logger from '../util/logger';
const dataContext = new UserData();
// Middleware to set bodyAttribs
export const bodyAttribs = async (ctx: Context, next: any) => {
ctx.state.bodyAttribs = ['handle', 'processUserOptions', 'name', 'email', 'pass', 'passConfirm'];
return await next();
};
...
So each controller provides the custom middleware that set the custom bodyAttribs I want to verify. You can use this approach to set and pass 1 to any number of extra params, whatever you need, in ctx.state which always goes on to next middleware in chain. Follow? :-)

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);
}),
);

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.

how to setup morgan-boddy in nestjs

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.

Using Service in Express Router

I am pretty new in the NodeJS but I would like to learn something new. I came from .NET fancy dependency injection, inversion of controll, microservice shiny world so I am trying write some service in TypeScript based on my previous experiences.
I am using express and express router to create some api. I have some methods in router which handles api calls and I want to use some kind of service object for data retrieving and manipulation.
I inject the service into the router using constructor injection but if I want to use my service it throws an error:
TypeError: Cannot read property 'layoutService' of undefined
I understood that the methods were called withouth context so I added .bind(this) to the each method regsitration and it works, but I dont know if it is the best way how to do it.
Does anyone have a better idea?
simplified server.ts
import express, { Router } from "express";
// inversion of controll
import container from "./ioc";
import { TYPE } from "./constants";
import IMyService from "./abstract/IMyService";
// import routers
import MyRouter from "./api/MyRouter";
app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const router: Router = express.Router();
const myRouter: MyRouter = new MyRouter(container.get<IMyService>(TYPE.IMyService));
app.use("/", router);
app.use("/api/v1/layouts", layoutRouter.router);
MyRouter.ts
import IMyService from "./abstract/IMyService";
import { Router, Request, Response } from "express";
import { inject } from "inversify";
import { TYPE } from "../constants";
export default class MyRouter {
public readonly router: Router;
private readonly myService: IMyService;
constructor(
#inject(TYPE.IMyService) myService: IMyService
) {
this.myService = myService;
this.router = Router();
this.routes();
}
public GetAll(req: Request, res: Response): void {
this.myService.getAll()
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
public GetOne(req: Request, res: Response): void {
const id: string = req.params.id;
this.myService.getOne(new ObjectID(id))
.then(data => {
const status: number = res.statusCode;
res.json({ status, data });
})
.catch(err => {
const status: number = res.statusCode;
res.json({ status, err });
});
}
routes(): void {
this.router
.get("/", this.GetAll)
.get("/:id", this.GetOne);
}
}
If you define your function with the arrow syntax (ES6), it will "bind" the context to it automatically and you won't need to bind them. But it will depends on your use case (ou might need to bind a different context)

TypeScript and Express, Testing and exporting a router module

I'm getting my head around Node, Express and TypeScript, in doing so I'm looking to build up some tests.
To follow best practices, SOLID etc I've separated my routers from my controllers.
I have an index.ts file
import { Router } from 'express';
import { TestController } from '../controllers/test.controller';
let testController = new TestController();
let index = Router();
/* GET home page. */
index.get('/', (req, res, next) => testController.get(req, res));
export default index;
I also have an index.spec.ts file
import assert = require('assert');
import * as mocha from 'mocha';
import { index } from '../routes/index';
import { TestController } from '../controllers/test.controller';
import { Request, Response } from 'express';
var sinon = require('sinon');
var should = require('should');
import app from '../App';
describe('Indexrouter', function () {
describe('#Calling get /', function () {
it('should call test controller get function.', function () {
});
});
});
When I try and run the tests via gulp i get the following messages
"D:/Dev/Learning/node/ts-express/src/routes/index.ts(4,7): Exported variable 'index' has or is using name 'Router' from external module \"express-serve-static-core\" but cannot be named."
"D:/Dev/Learning/node/ts-express/src/routes/index.spec.ts(4,10): Module '\"D:/Dev/Learning/node/ts-express/src/routes/index\"' has no exported member 'index'."
"D:/Dev/Learning/node/ts-express/src/app.ts(24,14): Cannot find name 'index'."
I can run the server ok and it will produce the correct endpoint with response. Although I do get the working about exported variable 'index'. It just seems that in the tests I can't import the index module for some reason.
How can I fix the module so I don't get the exported variable error.
How can I actually test that the index route calls the get method on the controller.
I have managed to get the controller tests working and the project as a whole can be seen here

Resources