I have an internal logger project.
Until now, we are managing the global parameters context of the logger with httpContext
import * as httpContext from 'express-http-context';
that httpContext basically creates a context for each HTTP request flow - and we add log parameters at the begging of the flow that will attach as parameter logs all the way.
This code is inside the internal logger project:
updating the log parameters:
setLogParameters(logParams: any): void {
if (logParams) {
const existingParams = httpContext.get('logParams');
logParams = { ...logParams, ...existingParams };
httpContext.set('logParams', logParams);
}
}
attaching the httpContext as logParams to the actual log info that the service uses.
const addAppNameFormat = format((info) => {
if (httpContext) {
info = {
...info,
...httpContext.get('logParams')
};
}
return info;
});
The problem with that solution - there are scenarios that don't trigger by HTTP request - for example, pulling a message from Pub Sub subscription) - in those scenarios, the HTTP Context variable is not initialized - because there is no new HTTP request.
I have tried to mimic the express-http-context behavior -
https://github.com/skonves/express-http-context/blob/master/index.js
and make on my own context object.
On the express-http-context - I based on the express server that listen to HTTP requests - that way it really open new session for each request:
//on my app.ts
this.app.use(httpContext.middleware);
//on express-http-context
'use strict';
const cls = require('cls-hooked');
const nsid = 'a6a29a6f-6747-4b5f-b99f-07ee96e32f88';
const ns = cls.createNamespace(nsid);
/** Express.js middleware that is responsible for initializing the context for each request. */
function middleware(req, res, next) {
ns.run(() => next());
}
How can I trigger a specific point to open a new session?
This for example doesn't work - I need to execute the middlware function - and it need to take parameter [next: NextFunction] on some way.
function handlePulledMessage(message: any, ....) {
loggerContext.middleware;
loggerContext.set('X', 100);
loggerContext.set('Y', 200);
loggerContext.set('Z', 300);
}
How can I do it?
Related
I have a service built using Node and Express and MongoDB as database. Service is hosted on IIS.
There is a side filter panel section in the application. Since that filters' master information does not change often (Data Size is in KBs), I use basic Node caching technique(no npm package) to avoid going to database on each page load request. Below is the sample Node code:
//main index.js file
SetFiltersList() function is called as Node service is first initialized on IIS, or, when app pool recycles.
(async () => {
await init.SetFiltersList();
})();
//init.js (utility file)
let filtersList = null; // filterList object that keeps list of Filters as cached object
const SetFiltersList = async (_error) => {
//This is a MongoDB database call
result = await defaultState.DEFAULT_STATE.GET("FiltersList");
filtersList = result.filters;
}
//Get filters call
const getFiltersList = () => filtersList;
module.exports = {
FiltersList: getFiltersList
};
//Controller.js
const GETFILTERLIST = async (req, res, next) => {
res.send(init.FiltersList());
}
//Controller Route
approuter.route('/GetFilterList/')
.get(Controller.GETFILTERLIST);
Problem
After few calls, Filters start returning null and strangely when I recycle the Application pool, the Filters starts coming again for sometime and this repeats after period of time.
Any thoughts whats going wrong here and how I can overcome this?
I will show you an example of what i'm trying to do :
server.ts
export class ExpressServer {
public readonly app: express.Application;
public readonly lbApp: ImportedApp;
private server?: Server;
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new ImportedApp(options);
this.app.get('/hello', async function (_req: Request, res: Response) {
//Here i'd like to call a model like User.findById() but can't figure out how to do it..
});
}
}
As you see in the comment i'm trying to access my models method to use them in my route (Like showing users informations on my view) But can't figure out how to do it. I'v already tryed to import the DataSource, the model, the controller but nothing's containing my methods (FindById, Create etc..)
If i find nothing i will have to use something like Axios or Request to request the ressource from the api instead of inside my code like await request('api/users/myusername)
In LoopBack 4, we use Repository design patter for accessing data. In order to find a user instance by its id, you need to obtain an instance of UserRepository via dependency injection. Quoting from https://loopback.io/doc/en/lb4/Repository.html:
Repositories are adding behavior to Models. Models describe the shape of data, Repositories provide behavior like CRUD operations. This is different from LoopBack 3.x where models implement behavior too.
UPDATED SOLUTION
To obtain an instance of a Repository class, you can use the Service Locator design pattern and get the instance from the per-request Context object provided by LoopBack's REST layer.
import {MIDDLEWARE_CONTEXT, RequestContext} from '#loopback/rest';
import {UserRepository} from '../repositories';
function expressHandler(req, res, next) {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get<UserRepository>('repositories.UserRepository');
const users = await userRepo.find({limit: 10});
// render your view
}
We are discussing how to make this use case easier to implement in GitHub pull request loopback-next#6793, feel free to join the discussion there.
ORIGINAL ANSWER
Instead of writing an Express route for your rendered pages, I recommend you to write a LoopBack 4 Controller instead; and inject Express Response object to allow you to render the HTML view, as explained in https://loopback.io/doc/en/lb4/Accessing-http-request-response.html#inject-http-response
import {Response, RestBindings, oas} from '#loopback/rest';
import {inject} from '#loopback/core';
import {UserRepository} from '../repositories';
export class PingController {
constructor(
#inject(RestBindings.Http.RESPONSE)
private response: Response
#repository(UserRepository)
public userRepository: UserRepository,
) {}
// Hide this endpoint from OpenAPI spec generated for the app
#oas.visibility('undocumented')
#get('/users')
list(): Response {
// Access User data via this.userRepository API
const users = await this.userRepository.find({limit: 10});
// Access the response object via `this.response`
this.response.render('users', {users});
// Return the HTTP response object so that LoopBack framework skips the
// generation of HTTP response
return this.response;
}
}
Having said that, if you already know how to access DataSource instances from your LB4 app in your Express routes, then you can instantiate Repository classes manually from your routes too:
const db = // your datasource
this.app.get('/hello', async function (_req: Request, res: Response) {
const repo = new UserRepository(db);
const users = await this.userRepository.find({limit: 10});
});
To me the solution is not working. Started from the express-composition example, i just need to access lb repositories from a generic express route outside of the lb4 request handler:
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new NoteApplication(options);
this.lbApp.basePath('')
// Expose the front-end assets via Express, not as LB4 route
this.app.use('/api', this.lbApp.requestHandler);
this.app.get('/hello', async (req: Request, res: Response) => {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get('repositories.UserRepository');
res.send('Hello world!');
});
}
the ctx in the line
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
is always undefined.
My main goal is to have routes not under /api that can still access lb4 repositories.
I have a singleton logger file. When a request comes into Express, I use middleware to set the request ID.
// Relevant parts of server.js
import express from 'express';
import requestIdMiddleware from './request-id-middleware';
const app = express();
app.use(requestIdMiddleware);
--
// Relevant parts of request-id-middleware.js
const uuid = require('uuid/v4');
const { setRequestId } = require('./logger');
module.exports = function(req, res, next) {
const id = uuid();
req.id = id;
// This sets a static variable on the plain logger object
setRequestId(id);
next();
};
--
// Relevant parts of logger.js
module.exports = {
request_id: null,
setRequestId: id => {
this.request_id = id;
},
log: message => {
// sends a log out using this.request_id, which is shared by all users of the server
}
}
Using the plain object now means everyone is sharing the same value. So despite each request calling setRequestId, it means if the first request takes longer than the second, it may use the second request's ID when referencing logger's value.
It seems I would need to make the logger a class instead of a plain object, instantiate it in my request middleware, and then pass the instance through to ensure unique request IDs across multiple calls from same request. Although unlikely, hoping to find a way around needing to pass a variable down into anything I want to log from.
in my application I instantiate an application wide object called controller. Also I'm starting a server. Since I want to keep redundancy low, on each request I want to instantiate a frontend to controller, which is a copy/reference to controller, but with an additional pool property, which contains request wide objects/configs and can be accessed from inside controller.
var applicationPool = new ObjectPool(); // container for objects
var controller = new Controller(); // application wide instance
var server = http.createServer();
applicationPool.set("myController", controller);
server.on("request",function(req,res){
var requestPool = new ObjectPool();
requestPool.set("request",req);
requestPool.set("response",res);
/*
* pool population
* routing
* controller resolving
* parameter resolving
*/
// frontend specific to current request
var frontend = applicationPool.get("myController").create(requestPool);
// hopefully finishes res
frontend.greetAction( parameters );
/*
* post response actions
*/
}
server.listen(3000);
And the Controller class:
function Controller(){
BaseController.call(this);
// ...
}
function greetAction( parameters ){
var res = this.getObjectPool().get("response"); // defined in BaseController
res.end(format("Greetings, %s!",parameters["name"]));
}
Controller.prototype = Object.create( BaseController.prototype );
Controller.prototype.greetAction = greetAction;
Additional my thoughts about a BaseController class:
function BaseController(){ ... }
function getObjectPool(){
return this.pool;
}
function create( pool ){
var frontend = Object.create( this.__proto__, this );
frontend.pool = pool;
return frontend;
}
BaseController.prototype.getObjectPool = getObjectPool;
BaseController.prototype.create = create;
This is were I got stuck. For what I tested. If I add pool to frontend it's also applied to the controller object as well. I'm thinking about creating a new object and append all properties of controller. I'm also having a glance at proxies, having controller as target and a get trap for getObjectPool.
I know modifying res directly is bad practice. I probably will return string/buffer instead. But the described problem stays. As I plan to embed other controllers.
I'm coming from PHP+Symfony where You have a Controller class with a getContainer method and shortcuts for core objects, doing the same thing.
Any thoughts are appreciated. Awhile I'm trying to solve this.
Cheers!
Ok I think I got an solution. It's kinda tricky since I'm actually saving all "protected" data in "__" property (this.__.pool). Here is the code for the working create function in this example:
function create( pool ){
return new Proxy(this,{
get: function(target, property){
if(property === 'pool') return pool;
return target[property];
}
});
}
This returns a Proxy (frontend) for controller. Everytime I access pool of frontend, the caller will get redirected to the assigned pool argument. Even inside the frontend object.
//...
var frontend = applicationPool.get("myController").create(requestPool);
frontend.greetAction( parameters ); // this.pool will be redirected to requestPool
//...
I will wait for other suggestions, before I check in as resolved.
Coming from a non Node background, my first instinct is to define my service as such
MyService.js
module.exports = new function(dbConnection)
{
// service uses the db
}
Now, I want one open db connection per request, so I define in middleware:
res.locals.db = openDbConnection();
And in some consuming Express api code:
api.js
var MyService = require(./services/MyService')
...
router.get('/foo/:id?', function (req, res) {
var service = new MyService(res.locals.db);
});
Now, being that Node's preferred method of dependency injection is via the require(...) statement, it seems that I shouldn't be using the constructor of MyService for injection of the db.
So let's say I want to have
var db = require('db');
at the top of MyService and then use somehow like db.current....but how would I tie the db to the current res.locals object now that db is a module itself? What's a recommended way of handling this kind of thin in Node?
Updated Answer: 05/02/15
If you want to attach a DB connection to each request object, then use that connection in your service, the connection will have to be passed to myService some how. The example below shows one way of doing that. If we try to use db.current or something to that effect, we'll be storing state in our DB module. In my experience, that will lead to trouble.
Alternatively, I lay out the approach I've used (and still use) in this previous answer. What this means for this example is the following:
// api.js
var MyService = require(./services/MyService')
...
router.get('/foo/:id?', function (req, res) {
MyService.performTask(req.params.id);
});
// MyService.js
var db = require('db');
module.exports = {
performTask: function(id)
{
var connection = db.getOpenConnection();
// Do whatever you want with the connection.
}
}
With this approach, we've decoupled the DB module from the api/app/router modules and only the module that actually uses it will know it exists.
Previous Answer: 05/01/15
What you're talking about could be done using an express middleware. Here's what it might look like:
var db = require('db');
// Attach a DB connection to each request coming in
router.use(req, res, next){
req.locals.db = db.getOpenConnection();
next();
}
// Later on..
router.get('/foo/:id?', function (req, res) {
// We should now have something attached to res.locals.db!
var service = new MyService(res.locals.db);
});
I personally have never seen something like new MyService before in express applications. That doesn't mean it can't be done, but you might consider an approach like this
// router.js
var MyService = require('MyService');
router.get('/foo/:id?', function (req, res) {
MyService.foo(res.locals.db);
});
// MyService.js
module.exports.foo(connection){
// I have a connection!
}