Hapi showing undefined variable inside handler - javascript

I'm using Hapi.js for a project and a config variable that I'm passing to my handler is coming up as undefined when I call my route. What am I doing wrong?
server.js
var Hapi = require('hapi');
var server = new Hapi.Server('0.0.0.0', 8080);
// passing this all the way to the handler
var config = {'number': 1};
var routes = require('./routes')(config);
server.route(routes);
server.start();
routes.js
var Home = require('../controllers/home');
module.exports = function(config) {
var home = new Home(config);
var routes = [{
method: 'GET',
path: '/',
handler: home.index
}];
return routes;
}
controllers/home.js
var Home = function(config) {
this.config = config;
}
Home.prototype.index = function(request, reply) {
// localhost:8080
// I expected this to output {'number':1} but it shows undefined
console.log(this.config);
reply();
}
module.exports = Home;

The issue is with the ownership of this. The value of this within any given function call is determined by how the function is called not where the function is defined. In your case above this was referring to the global this object.
You can read more on that here: What does "this" mean?
In short the solution to the problem is to change routes.js to the following:
var Home = require('../controllers/home');
module.exports = function(config) {
var home = new Home(config);
var routes = [{
method: 'GET',
path: '/',
handler: function(request, reply){
home.index(request, reply);
}
}];
return routes;
}
I've tested this and it works as expected. On a side note, you're missing out on a lot of hapi functionality by structuring your code in this way, I generally use plugins to register routes instead of requiring all routes as modules and using server.route().
See this project, feel free to open an issue if you have further questions on this: https://github.com/johnbrett/hapi-level-sample

Related

app.get is undefined (node.js)

In my app.js file I do the following...
var app = module.exports = express();
Then in my register.js file I am using get to render a page in the following way...
var module = require('../app');
module.app.get('/register', function (req, res) {
res.render('register.jade', { title: 'register' });
});
But I get the error that get cannot be used with app because it is undefined
If I change my get function to the following, everything works fine...
exports.register = function (req, res) {
res.render('register.jade', { title: 'register' });
};
and change my app.js to...
app.get('/register', reg.register);
... it will work.
But I want to be able to export app so I can use it in other routes. How do I properly export it?
EDIT
I just realized the proper way to to require the module is using var app = require('./app');.
The path I mentioned before is wrong.
But now I get an error cannot find module ./app, I have no idea why.
only things assigned to module.exports are visible outside of that JavaScript file. In your case that is returned value of express(). There is no module.app exported. Also, stop using module as variable name, because its one of the globals injected to the JavaScript file.
Better do something more clear and hand control to server script, which in turn will call all required setup functions on server run:
Structure:
project
|_controllers
| |_login.js
| |_register.js
|_routes.js
|_app.js
controllers/login.js :
/**
* Login user.
* #param req - express Request.
* #param res - express Response.
* #param next - calls next middleware.
*/
function login(req,res,next){
res.send("Login!");
}
// exports
module.exports = login;
controllers/register.js :
/**
* Register user.
* #param req - express Request.
* #param res - express Response.
* #param next - calls next middleware.
*/
function login(req,res,next){
res.send("Register!");
}
// exports
module.exports = register;
routes.js:
/**
* routes module.
*/
var routes = {};
/**
* Setup app routes.
* #param app - Express app instance.
*/
routes.setup = function setup(app){
app.post('/register',require('./controllers/register'));
app.post('/login',require('./controllers/login'));
}
// exports
module.exports = routes;
app.js:
var express = require('express');
var routes= require('./routes');
// init app
var app = express();
// setup routes
routes.setup(app);
// start server
app.listen(8080);
First thing I wanna say is what module.exports=... and exports.something=... do. module.exports is the object that would be import after require it. Let's say there is a file named X.js :
var A = {
name:'A'
};
module.exports = A;
Now we can import it in other file, suppose we have a file named Y.js :
var A = require('pathToX/X');
console.log(A.name);
So, in your code module.exports = express();, the object you've exported is the "express()" itself. Your should use it in other files (for example, your register.js file) in this way :
var module = require('../app');
module.get('/register', function (req, res) {
res.render('register.jade', { title: 'register' });
});
That is, when you call require('../app'), what you got is the express(). So your code module.app.get is equals to express().app.get, of cause there is no app property in express().
Besides, the module is predefined in node js, please don't use it as a variable name, you can change your register.js file to
var app = require('../app');
app.get('/register', function (req, res) {
res.render('register.jade', { title: 'register' });
});
And this is true : exports===module.exports, so you did the right way to import register in your code app.get('/register', reg.register);.
One more thing, exports={name:'A'} is NOT equals to module.exports={name:'A'}, that is pretty straightforward, exports is just a variable link to module.exports, change the exports link itself won't change module.exports.
In your app.js, try something more like:
module.exports = {
app: express()
}
Then when you do var module = require('./app'), module will contain an object which has an app property.
Note that I don't know what express() does, but I am assuming it is returning an object that has a get() function on it.
try this
//app.js
var express = require('express');
var app = module.exports = express();
//register.js
var module = require('./app.js');
//var http = require('http');
//var server = http.createServer(module);
module.get('/register', function(req,res,next) {
// body...
console.log("register");
});
//server.listen(1338);

module.exports object instance for the rest of the app to use

In Node, I have the following function/class which instantiates a logger object that I'd like to use in other functions/classes.
In server.js,
var loggerUtil = require('./utils/logger'),
logger;
module.exports = function(appInitCb, options) {
start: function() {
// Load .env
dotenv.config({
silent: true
});
// Merge the app configuration with default
options = _.merge({}, cfgDefaults, options);
// Create a logger
logger = loggerUtil.createLogger(options.logger).getLogger();
},
logger: logger
};
In main.js,
var server = require('./server');
var logger = require(./server').logger;
server.start({}, {});
logger.info('Hello World');
However, it results in the error TypeError: logger.info is not a function. Im guessing theres something wrong with the way I export. Any help is appreciated.
You need to export it like so
module.exports = function(appInitCb, options) {
return({
start: ...
log: ...
})
}
In your example you are not returning an object which unifies both functions.
You can then require this module like so (thanks #Nivesh):
var server = require('./server')();
server.start();
Update
A better approach would be to create an object and assign it directly to the module without the function:
var server = {
start: function(){...},
log: function(){...},
}
module.exports = server;
That way you can:
var server = require('./server');
server.start()
server.log()

HapiJS - placing controllers in routes

Hapi is something new for me and actually I'm trying to create a good structure for my project.
I've this file: routes/catalogs.js
"use strict";
const Boom = require('boom');
const uuid = require('node-uuid');
const Joi = require('joi');
exports.register = function(server, options, next) {
server.route({
method: 'GET',
path: '/catalogs',
handler: function(request, reply) {
var catalogs = server.app.mongodb.collection('catalogs');
catalogs.find({}).toArray(function(err, docs) {
reply(docs);
});
}
});
return next();
};
exports.register.attributes = {
name: 'routes-notifications'
}
But instead left all the code on the same file for routing ,I'd like to place the code that's do all the database task in a controller, something similar to this: https://github.com/agendor/sample-hapi-rest-api/blob/master/src/controllers/task.js
but I really can't understand how to do it maybe because on the example, the libs for database is very different and it's mess me. can someone help me to convert my controle to be used with config : {handler: ?
ty !
If you look at this file it shows how you can break mongodb handlers which are added to hapi routes here. The aim of this module was to generate data models and methods from json schemas.

Nodejs, express routes as es6 classes

I want to clean up my project a bit and now i try to use es6 classes for my routes. My problem is that this is always undefined.
var express = require('express');
var app = express();
class Routes {
constructor(){
this.foo = 10
}
Root(req, res, next){
res.json({foo: this.foo}); // TypeError: Cannot read property 'foo' of undefined
}
}
var routes = new Routes();
app.get('/', routes.Root);
app.listen(8080);
try to use the code to pin this:
app.get('/', routes.Root.bind(routes));
You can get out of the boilerplate using underscore bindAll function. For example:
var _ = require('underscore');
// ..
var routes = new Routes();
_.bindAll(routes, 'Root')
app.get('/', routes.Root);
I also found that es7 allows you to write the code in a more elegant way:
class Routes {
constructor(){
this.foo = 10
}
Root = (req, res, next) => {
res.json({foo: this.foo});
}
}
var routes = new Routes();
app.get('/', routes.Root);
This is happening because you've passed a method as a standalone function to express. Express doesn't know anything about the class that it comes from, therefore it doesn't know which value to use as this when your method is called.
You can force the value of this with bind.
app.get('/', routes.Root.bind(routes));
Or you can use an alternative construct for managing routes. You can still make use of a lot of the syntactic benefits for object oriented programming without classes.
function Routes() {
const foo = 10;
return {
Root(req, res, next) {
res.json({ foo });
}
};
}
const routes = Routes();
app.get('/', routes.Root);
app.listen(8080);
You won't have to worry about the value of this
It doesn't matter whether the function is called with new or not
You can avoid the complexity of calling bind on each route
There's a good list of resources here, on why ES6 classes are not as good as they might seem.
import express from 'express';
const app = express();
class Routes {
constructor(){
this.foo = 10
}
const Root = (req, res, next) => {
res.json({foo: this.foo}); // TypeError: Cannot read property 'foo' of undefined
}
}
const routes = new Routes();
app.get('/', routes.Root);
app.listen(8080);
Here is minor rewrite of the code, but as some of the answers here pointed out, the function itself when referenced like that in the route config is unaware of this and has to be binded. To get around this, instead of writing "normal" functions, you just have to write define "fat arrow" functions that automatically bind themselves and you are good to go!
Or if you don't like binding the context per routes, you can optionally bind it to methods in your class' constructor itself.
E.g:
constructor() {
this.foo = 10;
this.Root = this.Root.bind(this);
}
We recently refactored all our Express controllers to use a base controller class, and also ran into this issue. Our solution was to have each controller bind its methods to itself by calling the following helper method from the constructor:
/**
* Bind methods
*/
bindMethods() {
//Get methods
const proto = Object.getPrototypeOf(this);
const methods = [
...Object.getOwnPropertyNames(Controller.prototype),
...Object.getOwnPropertyNames(proto),
];
//Bind methods
for (const method of methods) {
if (typeof this[method] === 'function') {
this[method] = this[method].bind(this);
}
}
}
This ensures that both the parent Controller methods and any custom methods in the child controller class are bound correctly (e.g Foo extends Controller).
The above answers seem a bit over complicated. Checkout what I've done here:
class Routes {
constructor(req, res, next) {
this.req = req;
this.res = res;
this.next = next;
this.foo = "BAR"
// Add more data to this. here if you like
}
findAll (){
const {data, res,} = this; // Or just reference the objects directly with 'this'
// Call functions, do whaterver here...
// Once you have the right data you can use the res obejct to pass it back down
res.json ({foo: this.foo}); // Grabs the foo value from the constructor
}
}
Now when it comes to using this class you can do something along the lines of this:
var express = require('express');
var router = express.Router();
var {Routes} = require('./Routes');
router.get('/foo', (req, res, next) => {
new Routes(req, res, next).findAll();
});
I would seperate the two files, so that you just require the Routes class into your Router file.
Hope this helped!

Can't find controller in routes file hapijs

i'm fairly new to the whole Nodejs world and I'm trying to create a simple app with Hapi.js to get myself started around here. Anyways, I've got my routes file setup this way:
var usersController = require("./src/controllers/usersController.js");
exports.register = function(server, options, next) {
server.route([
{
method: 'POST',
path: '/register',
handler: usersController.register
},
]);
next();
};
exports.register.attributes = {
name: 'routes',
version: '0.0.1'
};
and then I have my controller
var Hapi = require('hapi');
var UserModel = require('./src/models/user.js');
function UsersController(){};
UsersController.prototype = (function(){
return {
register: function register(request, reply) {
var newUser = User({
name: request.params.name,
username: request.params.username,
password: request.params.password
});
newUser.save(function(err){
if (err) throw err;
console.log("You created a user, bruh");
})
},
}
})();
var usersController = new UsersController();
module.exports = usersController;
the error i'm getting from the console is "Cannot find module ./src/controllers/usersController.js". I even tried to type up the absolute location of the file inside the require and got the same error, so I must be failing somewhere else.
Thanks in advance
Paths are relative to the files, not to the project root. So for example in your controller you should use '../models/user.js':
var UserModel = require('../models/user.js');

Categories