I am new to NodeJS / Express and I have this issue related to global/module sharing of modules and configuration settings that I will try to explain here.
A simple example is trying to unit test a middleware module that includes other libraries. As follows:
config/settings.js
module.exports = {
...,
debugLevel: 3
};
middleware/auth.js
// Required modules for middleware
// ----------------------------------------------------------------------
var config = require('../config/settings');
var log = require('../libraries/logger')('middlewares/auth');
...
// Middleware methods
// ----------------------------------------------------------------------
var middleware = {};
middleware.apicall = function(req, res, next) {
...
log.info('Made api call');
};
libraries/logger.js
// External Dependencies
var winston = require('winston');
var winlogger = new (winston.Logger)({
transports: [
new (winston.transports.Console)()
]
});
winlogger.cli();
var logger = function(module, debug) {
this.module = module;
this.logger = winlogger;
this.debugLevel = debugLevel;
};
logger.prototype = {
module: '',
logger: null,
debugLevel: 3,
/**
* Generic Log function
*
* #param level
* #param str
* #param data
*/
debug: function(level, str, data) {
if ( this.debugLevel > 2) this.logger.log(level, this.module+' - '+str, data);
},
/**
* Specific function for info
*
* #param str
* #param data
*/
info: function(str, data) {
if ( this.debugLevel > 1 ) this.logger.info(this.module+' - '+str, data);
}
};
tests/api.js
const chai = require('chai');
const should = chai.should;
const expect = chai.expect;
const assert = chai.assert;
// Node Http Mocks
var httpMocks = require('node-mocks-http');
var middleware = require('../middlewares/auth');
describe("Test API Call", function() {
it("should give 200 over api call", function(done) {
var req = httpMocks.createRequest({
method: 'GET',
url: '/test',
params: {}
});
var res = httpMocks.createResponse();
middleware.apicall(req, res, function(res) { return res; });
assert.equal(res.statusCode, 200);
done();
});
});
Now all this works but the problem i am having is: how can I do the tests without the log line from apicall method showing up ?
Is there a way for the settings to be globally present ? Or a way to structure the app so that you can inject the settings coming from one single require in app.js ?
I would need to do, in tests/api.js, something like this:
var config = require('../config/settings');
config.debug = 0;
and inject it into the test probably. Anyone faced a similar challenge ?
ANSWER
Having not found a solution, I tried to make a series of tests and came up with a solution that works.
We make use of globals for this particular case. So:
bin\www
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
...
var http = require('http');
global.config = require('../config/settings');
global.log = require('../libraries/logger')('server');
...
app.js
// Node framework dependencies
// ----------------------------------------------------------------------
var express = require('express');
var config = global.config;
var path = require('path');
var log = global.log;
log.setLabel('app');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var cors = require('cors');
var validator = require('express-validator');
...
tests\api.js
const chai = require('chai');
const should = chai.should;
const expect = chai.expect;
const assert = chai.assert;
global.config = require('../config/settings');
config.debug = false;
global.log = require('../libraries/logger')('Test case');
...
// Node Http Mocks
var httpMocks = require('node-mocks-http');
var middleware = require('../middlewares/auth');
describe("Test API Call", function() {
it("should give 200 over api call", function(done) {
var req = httpMocks.createRequest({
method: 'GET',
url: '/test',
params: {}
});
var res = httpMocks.createResponse();
middleware.apicall(req, res, function(res) { return res; });
assert.equal(res.statusCode, 200);
done();
});
});
Related
I try to register pg-promise as hapi plugin.
However, the following error occurred.
TypeError: Cannot read properties of undefined (reading 'name')
at internals.Server.register (/home/kim.js/pgpromise/node_modules/#hapi/hapi/lib/server.js:456:42)
at init (/home/kim.js/pgpromise/hapi-pgTest.js:22:16)
My code is as follows and I also attach a reference site.
Hapi Tutorial - Getting Started
Hapi Tutorial - Plugins
'use strict';
const promise = require('bluebird');
const Hapi = require('#hapi/hapi');
const Pg = require('pg-promise')();
const postgreSql = {
host: '127.0.0.1',
port: 5432,
user: 'someone',
password: 'my_password',
database: 'my_db',
table_schema: 'public',
};
const init = async () => {
const server = Hapi.server({
port: 3000,
host: '192.168.9.23'
});
await server.register([{ // ERROR is Here !!
plugin: Pg,
options: {
promiseLib: promise
}
}]);
server.route({
method: 'GET',
path: '/',
handler: async (request, h) => {
let sql = 'SELECT FROM my_table limit 1';
let result = await pgp.one(sql).catch(error => { console.log(sql); });
return result;
}
});
const pgp = Pg(postgreSql);
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
English is not my first language, so please understand if the sentence is weird.
In my project, I used a plug-in called hapi-pg-promise before, but Hang occurred with hapi v20.
My mistake was that I try register the module, not the plugin.
So I referred to hapi plugin doc and modified my code by referring to hapi-pg-promise source code.
Make My hapi-pg-promise
'use strict';
/* this is original source
const Hoek = require('hoek');
const PgPromise = require('pg-promise');
*/
const Hoek = require('#hapi/hoek');
const PgPromise = require('pg-promise')();
const pkg = require('./package.json');
const DEFAULTS = {
init: {},
cn: undefined
};
module.exports = {
pkg,
register: async function (server, options) {
const opts = Hoek.applyToDefaults(DEFAULTS, options);
const pgp = require('pg-promise')(opts.init);
const db = pgp(opts.cn);
server.ext('onPreHandler', (request, h) => {
request.db = db;
return h.continue;
});
server.expose('db', db);
server.events.on('stop', pgp.end);
}
};
After that, I registered the plugin and it worked :)
/* NOT this
const Pg = require('pg-promise')();
*/
const Pg = require('./myPlugins/my-hapi-pg-promise');
my express js routes are giving me error 500 internal server error and I tried to console log the variables and nothing shows up
here are the express routes:
submitStar() {
this.app.post("/submitstar", async (req, res) => {
if(req.body.address && req.body.message && req.body.signature && req.body.star) {
const address = req.body.address;
const message = req.body.message;
const signature = req.body.signature;
const star = req.body.star;
try {
let block = await this.blockchain.submitStar(address, message, signature, star);
if(block){
return res.status(200).json(block);
} else {
return res.status(500).send("An error happened!");
}
} catch (error) {
return res.status(500).send(error);
}
} else {
return res.status(500).send("Check the Body Parameter!");
}
});
}
I keep getting the message "Check the Body Parameter!" in Postman while the message is actually correct
the app.js:
const express = require("express");
const morgan = require("morgan");
const bodyParser = require("body-parser");
/**
* Require the Blockchain class. This allow us to have only one instance of the class.
*/
const BlockChain = require('./src/blockchain.js');
class ApplicationServer {
constructor() {
//Express application object
this.app = express();
//Blockchain class object
this.blockchain = new BlockChain.Blockchain();
//Method that initialized the express framework.
this.initExpress();
//Method that initialized middleware modules
this.initExpressMiddleWare();
//Method that initialized the controllers where you defined the endpoints
this.initControllers();
//Method that run the express application.
this.start();
}
initExpress() {
this.app.set("port", 8000);
}
initExpressMiddleWare() {
this.app.use(morgan("dev"));
this.app.use(bodyParser.urlencoded({extended:true}));
this.app.use(bodyParser.json());
}
initControllers() {
require("./BlockchainController.js")(this.app, this.blockchain);
}
start() {
let self = this;
this.app.listen(this.app.get("port"), () => {
console.log(`Server Listening for port: ${self.app.get("port")}`);
});
}
}
new ApplicationServer();
what could be wrong with server?
Made some changes in the code.
const express = require("express");
const morgan = require("morgan");
const bodyParser = require("body-parser");
/**
* Require the Blockchain class. This allow us to have only one instance of
* the class.
*/
const BlockChain = require('./src/blockchain.js');
class ApplicationServer {
constructor() {
//Express application object
this.app = express();
//Blockchain class object
this.blockchain = new BlockChain.Blockchain();
//Method that initialized the express framework.
this.initExpress();
//Method that initialized middleware modules
this.initExpressMiddleWare();
//Method that initialized the controllers where you defined the endpoints
this.initControllers();
//Method that run the express application.
this.start();
}
initExpress() {
this.app.set("port", 8000);
}
initExpressMiddleWare() {
this.app.use(morgan("dev"));
this.router = express.Router();
this.router.use(bodyParser.urlencoded({extended:true}));
this.router.use(bodyParser.json());
}
initControllers() {
require("./BlockchainController.js")(this.app, this.blockchain);
// console.log(this.app.route(),"this.app")
}
start() {
let self = this;
this.router.post('/',(req,res) => {
console.log(req.body.id,"body");
res.status(200).send('Hello');
})
this.app.use(this.router);
this.app.listen(this.app.get("port"), (req,res) => {
console.log(`Server Listening for port: ${self.app.get("port")}`);
});
}
}
new ApplicationServer();
I ran into a problem and I feel that node-http-proxy is changing my target links. I got a few examples below.
I am using express as my server and using Metaweather API .
The problem is that I was able to get data from the endpoints below
https://www.metaweather.com/api/location/2487956/
https://www.metaweather.com/api/location/2487956/2013/4/30/
But when I try to call the API from https://www.metaweather.com/api/location/search/?lattlong=36.96,-122.02
It fails with status code 500 which I lead me thinking that node-http-proxy added some values after 122.02 as it was not closed with /
server.js
const express = require("express");
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const httpProxy = require("http-proxy");
const proxyOptions = {
changeOrigin: true
};
const apiProxy = httpProxy.createProxyServer(proxyOptions);
const apiUrl =
"https://www.metaweather.com/api/location/search/?lattlong=36.96,-122.02";
/*
https://www.metaweather.com/api/location/search/?lattlong=36.96,-122.02 - failed with 500
https://www.metaweather.com/api/location/2487956/ - passed
https://www.metaweather.com/api/location/2487956/2013/4/30/ - passed
*/
app
.prepare()
.then(() => {
const server = express();
server.use("/api", (req, res) => {
console.log("Going to call this API " + apiUrl);
apiProxy.web(req, res, { target: apiUrl });
});
server.get("*", (req, res) => {
return handle(req, res);
});
server.listen(3000, err => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
Thanks for looking into this question.
I have reproduced where this is happening in node-http-proxy.
In common.js there is a function called urlJoin which is appending the req.url to the end of the target url.
I'm not exactly sure what the intent is, but it's a start.
Here's my test:
const urlJoin = function() {
//
// We do not want to mess with the query string. All we want to touch is the path.
//
var args = Array.prototype.slice.call(arguments),
lastIndex = args.length - 1,
last = args[lastIndex],
lastSegs = last.split('?'),
retSegs;
args[lastIndex] = lastSegs.shift();
//
// Join all strings, but remove empty strings so we don't get extra slashes from
// joining e.g. ['', 'am']
//
retSegs = [
args.filter(Boolean).join('/')
.replace(/\/+/g, '/')
.replace('http:/', 'http://')
.replace('https:/', 'https://')
];
// Only join the query string if it exists so we don't have trailing a '?'
// on every request
// Handle case where there could be multiple ? in the URL.
retSegs.push.apply(retSegs, lastSegs);
return retSegs.join('?')
};
let path = urlJoin('/api/location/search/?lattlong=36.96,-122.02', '/');
console.log(path);
// /api/location/search/?lattlong=36.96,-122.02/
I am building an Express app. I currently have an error in the following file, factoryRepository.js
let appReference = null;
module.exports.init = (app) => {
appReference = app;
};
module.exports.getRepositoryFactory = () => {
let repositoryFactory = {
getUserRepository: () => {
return require("./UserRepository").init(appReference.get('models').User);
}
};
return repositoryFactory;
};
appReference.get throws a TypeError because appReference is still null even after I have called module.exports.init somewhere else.
I have tried to make a function that returns appReference so I can see in what state it is. I have been able to get app rather than null, it is only in the context of getUserRepository that it stays null.
The faulty line is only called when I ping a certain route.
EDIT:
This is app.js, the context from which module.exports.init is being called
import express from 'express';
import passport from 'passport';
import config from './config/config';
let app = express();
// Setup models
app.set('models', require('./app/models'));
require('./app/repo/repositoryFactory').init(app);
// Setup config
require('./config/init')(app);
// Setup routes
require('./app/routes')(app, passport);
// Setup passport
require('./app/auth')(passport, config);
// Route to ends
require('./config/endpoints')(app);
export default app;
public.js is the logic given to my router, and it is here where I call the faulty code with repositoryFactory.getUserRepository()
let repositoryFactory = require('../repo/RepositoryFactory').getRepositoryFactory();
module.exports.doLogin = (req, res) => {
let success = () => {
res.redirect('/');
};
let error = (message) => {
res.status(500).json(message);
};
let userRepository = repositoryFactory.getUserRepository();
userRepository.findOrCreate({
facebookId: req.user.id,
displayName: req.user.displayName
}, success, error);
};
module.exports.doLogout = (req, res) => {
req.logout();
res.json({
success: true,
message: 'You\'ve succesfully logged out.'
});
};
This looks like a scope issue. Let's say we simplify your three files as follows, and also put them all in the same directory (for simplicity here):
app.js:
'use strict';
const express = require('express');
let app = express();
require('./repositoryFactory').init(app);
const myPublic = require('./public');
repositoryFactory.js:
'use strict';
let appReference = null;
module.exports.init = (app) => {
appReference = app;
};
module.exports.getRepositoryFactory = () => {
return appReference ? 'I have an app reference!' : 'No app reference!';
};
public.js:
'use strict';
let repositoryFactory = require('./RepositoryFactory').getRepositoryFactory();
console.log(repositoryFactory);
This is going to log No app reference! because the repositoryFactory is not a singleton. The repositoryFactory instance in app.js is a different instance than the repositoryFactory instance in public.js.
One solution would be to pass a parameter for the repositoryFactory instance. repositoryFactory.js would be unchanged, but app.js might look like this:
'use strict';
const express = require('express');
let app = express();
const repositoryFactory = require('./repositoryFactory')
repositoryFactory.init(app);
const myPublic = require('./public');
myPublic.init(repositoryFactory);
myPublic.log();
And the corresponding public.js might look like this:
'use strict';
let repositoryFactory = null;
module.exports.init = (myRepositoryFactory) => {
repositoryFactory = myRepositoryFactory.getRepositoryFactory();
}
module.exports.log = () => {
console.log(repositoryFactory);
}
I'm new to Node and I'm trying to create an MVC app with ExpressJS (http://expressjs.com/). I'm using the same folder structure as the MVC example (https://github.com/visionmedia/express/tree/master/examples/mvc) on GitHub.
In my controllers folder, I have 2 folders: main and system. What I'd like is to have a base controller defined in /controllers/system/index.js and have /controllers/main/index.js inherit the system controller. Every other module will extend system and override a few functions to generate a page.
In another tutorial I found the following code.
Base.js
var _ = require("underscore");
module.exports = {
name: "base",
extend: function(child) {
return _.extend({}, this, child);
},
run: function(req, res, next) {
}
};
Home.js
var BaseController = require("./Base"),
View = require("../views/Base"),
model = new (require("../models/ContentModel"));
module.exports = BaseController.extend({
name: "Home",
content: null,
run: function(req, res, next) {
model.setDB(req.db);
var self = this;
this.getContent(function() {
var v = new View(res, 'home');
v.render(self.content);
})
},
getContent: function(callback) {
var self = this;
this.content = {};
model.getlist(function(err, records) {
if(records.length > 0) {
self.content.bannerTitle = records[0].title;
self.content.bannerText = records[0].text;
}
model.getlist(function(err, records) {
var blogArticles = '';
if(records.length > 0) {
var to = records.length < 5 ? records.length : 4;
for(var i=0; i<to; i++) {
var record = records[i];
blogArticles += '\
<div class="item">\
<img src="' + record.picture + '" alt="" />\
' + record.title + '\
</div>\
';
}
}
self.content.blogArticles = blogArticles;
callback();
}, { type: 'blog' });
}, { type: 'home' });
}
});
How do you do this without Underscore's extend function? Does Express have a built in method to extend modules? I'm using doT.js for templating so I don't want to include another large library for one function.
Thanks!
Edit: Had to make a few changes to get the Base code from dc5 to work. System works but for main I'm getting this error on the inherits call:
util.js:555
ctor.prototype = Object.create(superCtor.prototype, {
^
TypeError: Object prototype may only be an Object or null
at Function.create (native)
at Object.exports.inherits (util.js:555:27)
/controllers/system/index.js:
var util = require( 'util' ),
system = { };
system.index = function( req, res, next ) {
res.render( 'main' );
};
module.exports = system;
/controllers/main/index.js:
var util = require( 'util' ),
system = require( './../system/index' ),
main = { };
util.inherits( main, system );
module.exports = main;
You can use util.inherits for what you've described. It isn't an replacement for _.extend(), but all that is needed for the example above is straight inheritance.
Usage: util.inherits(constructor, superConstructor)
base.js:
var util = require('util');
function Base() {…}
Base.prototype.run = function(req,res,next) {…}
module.exports = Base;
home.js:
var util = require('util');
var BaseController = require("./base");
function Home() {…}
util.inherits(home, BaseController);
Home.prototype.run = function(req,res,next) {…}
Or a standard JS inheritance pattern:
base.js:
function Base() {…}
Base.prototype.run = function(req,res,next) {…}
module.exports = Base;
home.js:
var BaseController = require("./base");
function Home() {
BaseController.apply(this, arguments);
}
Home.prototype = Object.create(BaseController.prototype);
Home.prototype.run = function(req,res,next) {…}
From the updated samples in the question, the modules should look like this:
System:
var util = require('util');
function System() {
this.txt = "hello from ";
this.name = "System";
}
System.prototype.sayHello = function() {
console.log(this.txt + this.name);
}
System.prototype.run = function(req,res,next) {
this.sayHello();
console.log('calling ==> overrideMe');
this.overrideMe();
console.log('calling ==> noOverride');
this.noOverride();
next ? next() : "";
}
System.prototype.overrideMe = function() {
console.log('System.overrideMe');
}
System.prototype.noOverride = function() {
console.log('System.noOverride');
}
module.exports = System;
Main:
var util = require('util');
var System = require("../system/");
function Main() {
// Makes sure the System constructor is run
System.apply(this, arguments);
this.name = "Main";
}
util.inherits(Main, System);
Main.prototype.run = function(req,res,next) {
this.sayHello();
console.log('calling ==> overrideMe');
this.overrideMe();
console.log('calling ==> noOverride');
this.noOverride();
next ? next() : "";
}
Main.prototype.overrideMe = function() {
console.log('Main.overrideMe');
}
module.exports = Main;
app.js in the root - a simplified express server:
var System = require('./controllers/system');
var Main = require('./controllers/main');
var express = require('express');
var path = require('path');
var http = require('http');
var app = express();
var server;
var system = new System();
var main = new Main();
app.configure(function() {
"use strict";
app.set('port', process.env.PORT || 3000, '127.0.0.1');
app.use(system.run.bind(system));
app.use(main.run.bind(main));
app.use(app.router);
//app.use(express.compress());
app.use(express.static(path.join(__dirname, '..', 'public'), {redirect: false}));
app.use(express.static(path.join("/Users/dave/personal/playground/yo"), {redirect: false}));
});
server = http.createServer(app);
server.listen(app.get('port'), function() {
"use strict";
console.log("Express server listening on port " + app.get('port'));
});
From the browser access: http://localhost:3000/
Console output:
Express server listening on port 3000
hello from System
calling ==> overrideMe
System.overrideMe
calling ==> noOverride
System.noOverride
hello from Main
calling ==> overrideMe
Main.overrideMe
calling ==> noOverride
System.noOverride
Important
Because this example is using instances of System and Main to provide routes, the run method must be bound to the instance when passing it to express. See: MDN .bind documentation for more information.
To avoid complicated configuration and workarounds, use ZinkyJS, it gives a better approach to make modules inheritance possible.