URL routing in Node.js - javascript

Homework done:
The Node Beginner Book
How do I get started with Node.js [closed]
Structuring handlers in Node
Backstory: I wanted to try and write my own framework but I'm running into some troubles, most likely due to not understanding it fully.
What I want to achieve is a syntax that looks like this:
var app = require('./app'); //this part is understood and it works in my current code.
app.get('/someUrl', function(){ //do stuff here });
app.post('/someOtherUrl', function(){ //do stuff here });
I know of the Express-framework that has this same syntax but reading their source code still eludes me.
This might be a trivial task to achieve but I simply can't produce it, yet.
Trying to require('./app'); in a file deeper in the application produces a undefined object, so I'm guessing that a server is a singleton object.
So what have I tried?
My current code looks like this, and somehow I feel like this is the way to go, but I can't apparently do it like this.
I'm omitting all the require(); statements to keep it more readable.
server.js:
var app = module.exports = {
preProcess: function onRequest(request, response){
processor.preRequest(request); //this object adds methods on the request object
var path = urllib.parse(request.url).pathname;
router.route(urls, path, request, response);
},
createServer: function() {
console.log("Server start up done.");
return this.server = http.createServer(this.preProcess);
}
};
exports.app = app;
At the time of writing I'm experimenting with extending this object with a get() method.
index.js:
var app = require('./server');
app.createServer().listen('1337');
the router.route() bit basically sends the request onward into the application and inside the router.js-file I do some magic and route the request onward to a function that maps (so far) to the /urlThatWasRequested
This is the behavior I'd like to leave behind.
I know this might be a pretty tall order but all my code is easily discardable and I'm not afraid of rewriting the entire codebase as this is my own project.
I hope this is sufficient in explaining my question otherwise, please say what I should add to make this a bit more clear.
Thanks in advance!

I'm not exactly sure what your question is, but here's some thoughts:
1) You are creating a circular reference here:
var app = module.exports = {
// some other code
}
exports.app = app;
You add app as a property of app. There's no need for the last line.
2) You need to hold handlers in app. You can try something like this:
var app = module.exports = {
handlers : [],
route : function(url, fn) {
this.handlers.push({ url: url, fn: fn });
},
preProcess: function onRequest(request, response){
processor.preRequest(request);
var path = urllib.parse(request.url).pathname;
var l = this.handlers.length, handler;
for (var i = 0; i < l; i++) {
handler = this.handlers[i];
if (handler.url == path)
return handler.fn(request, response);
}
throw new Error('404 - route does not exist!');
},
// some other code
}
Note that you may alter this line: if (handler.url == path) in such a way that handler.url is a regular expression and you test path against it. Of course you may implement .get and .post variants, but from my experience it is easier to check whether a request is GET or POST inside the handler. Now you can use the code above like this:
app.route('/someUrl', function(req, res){ //do stuff here });
The other thing is that the code I've shown you only fires the first handler for a given URL it matches. You would probably want to make more complex routes involving many handlers (i.e. middlewares). The architecture would be a bit different in that case, for example:
preProcess: function onRequest(request, response){
var self = this;
processor.preRequest(request);
var path = urllib.parse(request.url).pathname;
var l = self.handlers.length,
index = 0,
handler;
var call_handler = function() {
var matched_handler;
while (index < l) {
handler = self.handlers[index];
if (handler.url == path) {
matched_handler = handler;
break;
}
index++;
}
if (matched_handler)
matched_handler.fn(request, response, function() {
// Use process.nextTick to make it a bit more scalable.
process.nextTick(call_handler);
});
else
throw new Error('404: no route matching URL!');
};
call_handler();
},
Now inside your route you can use
app.route('/someUrl', function(req, res, next){
//do stuff here
next(); // <--- this will take you to the next handler
});
which will take you to another handler (or throw exception if there are no more handlers).
3) About these words:
Trying to require('./app'); in a file deeper in the application produces a undefined
object, so I'm guessing that a server is a singleton object.
It isn't true. require always returns the reference to the module object. If you see undefined, then you've messed up something else (altered the module itself?).
Final note: I hope it helps a bit. Writing your own framework can be a difficult job, especially since there already are excelent frameworks like Express. Good luck though!
EDIT
Implementing .get and .set methods is actually not difficult. You just need to alter route function like this:
route : function(url, fn, type) {
this.handlers.push({ url: url, fn: fn, type: type });
},
get : function(url, fn) {
this.route(url, fn, 'GET');
},
post : function(url, fn) {
this.route(url, fn, 'POST');
},
and then in routing algorithm you check whether type property is defined. If it is not then use that route (undefined type means: always route). Otherwise additionally check if a request's method matches type. And you're done!

Related

How to test function calling multiple other functions?

I am writing an ExpressJS Middleware that modifies request object a bit and checks if user has access to that specific page. I have a problem with unit-testing it. I've written separate tests for every method except one: handler. How can I test handler function? Should I test it? Or should I just ignore it with istanbul ignore next as I've got every other function covered? Or maybe I should somehow rewrite my handler function to make it testable?
class Example {
constructor(request, response, next, userAccountService) {
this.req = request;
this.res = response;
this.next = next;
this.userAccountService = userAccountService;
}
removeTokenFromQuery() {
delete this.req.query.token;
}
isValidRequest() {
if (!this.req.secure) {
return false;
}
if (typeof this.req.query.token !== 'undefined') {
return false;
}
if (typeof this.req.query.unsupportedQueryParam !== 'undefined') {
return false;
}
return true;
}
isPageAccessibleForUser() {
return this.userAccountService.hasAccess('example');
}
async handler() {
this.removeTokenFromQuery();
if (!this.isValidRequest()) {
throw new Error('Invalid request');
}
if (!this.isPageAccessibleForUser()) {
this.res.statusCode(500);
this.res.end();
return;
}
this.next();
}
}
Then it's called as Express middleware:
this.app.use((res, req, next) => {
const exampleObj = new Example(res, req, next, userAccServ);
exampleObj.handler();
});
Should I test it?
Yes, based on your example handler contains (what looks to be) some critical business logic. It is responsible for orchestrating:
removing a token from a request (security)
determining if a request is valid (security/auth)
determining if a page is accessible for a user (security/auth)
If this function was not tested, a future engineer may make a modification to this important function, and they wouldn't receive any feedback on their change. Suppose that from human error they accidentally removed the isValidRequest check? or removed the !. However unlikely the risk associated with this happening may be catastrophic when compared to the relatively small amount of effort necessary to test this.
How can I test handler function?
The next question is how you actually test this :) I would opt to test this at the lowest "level" possible lower (unit tes this method by invoking it directly vs higher (going through the express framework).
As you mentioned, there are tests for the implementations of each one of the functions that handler delegates to, IMO the important thing to test in handler is the flow and NOT the implementations (since those are already well tested).
describe('handler()', () => {
it('removes token from query');
it('errors on invalid request');
it('returns 500 status code when page is inaccessible');
it('continues with .next() when request is valid and page is accessible');
})
In order to do this I would instantiate Example and then patch the methods necessary in order to create the correct flow for your handler() tests. So for the invalid request test this might look like:
const example = new Example();
sinon.stub(example, "isValidRequest").returns(false);
If this isn't stubbed than these tests essentially duplicate the other tests (by testing the actual implementation). Using stubs allows for the implementation of isValidRequest to change while still having unit test protection in handler

Expressjs middleware keeps variable changed

I am trying to do a simple thing which is obvious I believe in the code below:
module.exports = function(req, res, next) {
var dop = require('../../config/config').DefaultOptions;
console.log(require('../../config/config').DefaultOptions);
console.log(dop);
dop.firstPage = 'test.sjs';
next();
};
This is an Expressjs middle ware which is very simple but the interesting point is that next time I load a page both of the console.log results has been changed to 'firstPage: test.sjs'. It shouldn't act like this and it should only change the dop variable.
Anyone with the knowledge why this creepy thing is happening?
Thank you
The main issue is require() is cached, so require('../../config/config') returns reference to the same instance, and as a result changing in one place causes all other references and subsequent requires to get that modified instance.
The simplest solution would be have a function in config to return a config object, that way every time invoking the get config function you will get a new instance with essentially the same content. I.e.:
config.js:
module.exports = {
getDefaultOptions: function(){
return {foo: 'bar', ip: '1.1.1.1'}
}
};

Accessing Post html from Ghost Blog Custom Handlebars Helper

Ghost Blog has limited functionality when it comes to outputting content to a post and it's typically done through the {{content}} helper. I am trying to add more nuanced capabilities to the content helper by creating my own handlebars helpers to output blocks of content from within the ghost {{content}} helper.
I've been making use of these two resources to create my own solutions to the problem https://www.neoito.com/ghost-cms-on-steroids-with-custom-block-helpers/
https://github.com/TryGhost/Ghost/wiki/Apps-Getting-Started-for-Ghost-Devs
Everything works fine for the most part, but I've hit a snag when trying to port html from the post {{content}} to the handlebars helper I've registered. I managed to make an ajax call using jquery from the back-end by npm installing it within the registered helper folder. The folder is set up via the method described in the second link (creating an App within the ghost content folder).
in the index file, the helper is stored in a separate function and called when the app activates. My problem is getting the helper function to accept the ajax call and extract the html from the returned value.
I'm not set on this method and there is a way that it has been done in the first link (however, it was created to solve the problem with an older versions of ghost - pre 1.0 and I'm making use of Ghost 1.2.0 so I'm aware of some "breaking changes" have been made).
I need a way to extract the post html from the server side of things (if its even possible). The internal api does not work for me unless it is called inside the activate function but I cant seem to get it to work in the helper function which is outside of the activate functions scope... I'd love some help on this.
Here my index file to give some context to this. If you need any more information, let me know and I'll post (right now I cant think of anything else you might need)
const $ = require('jquery');
var App = require('ghost-app'),
hbs = require('express-hbs'),
ghost_api = require('ghost/core/server/public/ghost-sdk'),
proxy = require('ghost/core/server/helpers/proxy'),
helpers_briefcase;
helpers_briefcase = App.extend({
// content: function () {$.get(ghost.url.api('posts', {formats:["html"]})).done(function (data){
// console.log('posts', data.posts["html"]);
// return ('posts', data.posts["html"])
// console.log('it worked');
// debug('it worked');
// }).fail(function (err){
// console.log(err);
// });
// },
//Filter handling
filters: {
ghost_head: 'handleGhostHead',
ghost_foot: 'handleGhostFoot'
},
handleGhostHead: function () {},
handleGhostFoot: function () {},
install: function () {
},
uninstall: function () {
},
//Register Handlebars Helpers on activate
activate: function (posts) {
//Test for getting post content
this.ghost.api.posts.read(0).then(function (post) {
console.log(post.title);
return post.title
});
this.ghost.helpers.register('content_block', this.content_block_helper);
this.ghost.helpers.register('if_eq', this.if_eq);
},
deactivate: function () {
},
content_block_helper: function(node, posts) {
var content = post.data.root.post.html;
var regexstring = '<content_block'+ node +'>[\\s\\S]*?<\/content_block'+
node + '>'
var regexp = new RegExp(regexstring);
if(content.match(regexp)){
var match = content.match(regexp)
match = match.replace('<content_block'+ node + '>', '');
match = match.replace('</content_block'+ node + '>', '');
return new hbs.SafeString(match)
} else {
return('My first Handlebars Helper');
}
},
if_eq: function(a, b, opts) {
if (a == b) {
return opts.fn(this);
} else {
return opts.inverse(this);
}
}
});
module.exports = helpers_briefcase;
Thanks in advance.
Disclaimer: I consider myself something between a dedicated hobbyist and an expert, so before you go pointing out what javascript sins I've committed, please keep this in mind (go easy with pointing out errors outside of the problem I'm trying to solve) *thanks S.O Community.

Overriding functions in other modules in node.js

I'm trying to stub a function with nodeunit in a Node.js app. Here's a simplified version of what I'm trying to do:
In lib/file.js:
var request = require('request');
var myFunc = function(input, callback){
request(input, function(err, body){
callback(body);
});
};
In test/test.file.js:
var file = require('../lib/file');
exports['test myFunc'] = function (test) {
request = function(options, callback){
callback('testbody');
};
file.myFunc('something', function(err, body){
test.equal(body, 'testbody');
test.done();
});
};
It seems like I'm not overriding request properly, because when I try to run the test, the actual non-stub request is getting called, but I can't figure out what the correct way to do it is.
EDIT:
To expand on Ilya's answer below, with my example above.
in lib/file/js:
module.exports = function(requestParam){
return {
myFunc: function(input, callback){
requestParam(input, function(err, body){
callback(body);
});
}
}
}
Then in test/test.file.js:
var fakeRequestFunc = function(input, callback){
// fake request function
}
var file = require('../lib/file')(fakeRequestFunc)(
//test stuff
}
As you noticed, variables declared in one module, cannot easily be accessed from another module. In such cases, you have two common variants:
1) Declare everything you need in every module (not your case, I suppose)
2) Pass parameters to a function
var ab = "foo",
index = require('/routes/index')(ab);
When you call a function form a module, you may pass it 'request' or any other vars or object you need.
I've run into similar issue. After exploring request module code my solution was using request.get instead of request in my code (do exactly the same). And then stub it in test like that: https://github.com/anatoliychakkaev/resizer-app/blob/master/test/resizer.js#L25
It also possible to stub result of 'require' method in nodejs. Check sources on lib/module.js to manage how to do it. It should be something like:
require('module')._cache['/path/to/request.js'] = your_stub
But I don't like this solution because it doesn't work in 100% of cases and may stop working in future versions of node (this is not public api), so you should use this way only in case when it's not possible to use other ways of stubbing.

Javascript object member function referred to globally not recognized in callback

I'm having a problem with the following Javascript code (Phonegap in Eclipse):
function FileStore(onsuccess, onfail){
//chain of Phonegap File API handlers to get certain directories
function onGetSupportDirectorySuccess(dir){
//stuff
onsuccess();
}
function getDirectory(dir){
return "something" + dir;
}
}
var onFileStoreOpened = function(){
if (window.file_store instanceof FileStore){
console.log('window.file_store is a FileStore');
console.log(window.file_store.getDirectory('something'));
}
}
var onDeviceReady = function(){
window.file_store = new FileStore(onFileStoreOpened, onFileStoreFailure);
}
Here, I want to do some things to initialize file services for the app, and then use them in my initialization from the callback. I get the following error messages in LogCat:
07-03 06:26:54.942: D/CordovaLog(223): file:///android_asset/www/index.html: Line 40 : window.file_store is a FileStore
07-03 06:26:55.053: D/CordovaLog(223): file:///android_asset/www/cordova-1.8.1.js: Line 254 : Error in success callback: File7 = TypeError: Result of expression 'window.file_store.getDirectory' [undefined] is not a function.
After moving the code around and stripping out everything in getDirectory() to make sure it was valid, I'm not even sure I understand the error message, which suggested to me that getDirectory() is not seen as a member function of window.file_store, even though window.file_store is recognized as a FileStore object. That makes no sense to me, so I guess that interpretation is incorrect. Any enlightenment will be greatly appreciated.
I've since tried the following:
window.file_store = {
app_data_dir : null,
Init: function(onsuccess, onfail){
//chain of Phonegap File API handlers to get directories
function onGetSupportDirectorySuccess(dir){
window.file_store.app_data_dir = dir;
console.log("opened dir " + dir.name);
onsuccess();
}
},
GetDirectory : function(){
return window.file_store.app_data_dir; //simplified
}
}
var onFileStoreOpened = function(){
var docs = window.file_store.getDirectory();
console.log('APPDATA: ' + docs.fullPath);
}
var onDeviceReady = function() {
window.file_store.Init(onFileStoreOpened, onFileStoreFailure);
}
and I get
D/CordovaLog(224): file:///android_asset/www/base/device.js: Line 81 : opened dir AppData
D/CordovaLog(224): file:///android_asset/www/cordova-1.8.1.js: Line 254 : Error in success callback: File7 = TypeError: Result of expression 'docs' [null] is not an object.
All I want to do here is make sure certain directories exist (I've removed all but one) when I start, save the directory object for future use, and then retrieve and use it after all initialization is done, and I don't want everything in the global namespace. Of course I would like to be able to use specific instances when necessary, and I'm disturbed that I can't make it work that way since it demonstrates there is a problem with my understanding, but I can't even get this to work with a single, global one. Is this a Javascript problem or a Phonegap problem?
As it stands, your getDirectory function is a private function within FileStore. If you wanted to make it a 'member' or 'property' of FileStore, you would need to alter it a little within FileStore to make it like this:
this.getDirectory = function(dir){ };
or leave it how it is and then set a property....
this.getDirectory = getDirectory();
this way when new FileStore is called it will have getDirectory as a property because the 'this' keyword is always returned when calling a function with 'new'
Hope this quick answer helps. There's lots of stuff on the goog about constructor functions.
You understand it correctly. The getDirectory as it stands is a private function and cannot be called using the file_store instance.
Try this in the browser.
function FileStore(onsuccess, onfail){
function onGetSupportDirectorySuccess(dir){
//stuff
onsuccess();
}
this.getDirectory = function (dir){
return "something" + dir;
}
}
window.file_store = new FileStore('', ''); //the empty strings are just placeholders.
if (window.file_store instanceof FileStore){
console.log('window.file_store is a FileStore');
console.log(window.file_store.getDirectory('something'));
}
This will prove that the basic js code is working fine. If there still is a problem while using it in PhoneGap, comment.

Categories

Resources