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');
Related
I am writing a node/express rest api.
Hitting,
http://localhost:5000/api/news
and
http://localhost:5000/api/news/?id=c5f69d56be40e3b56e55d80
both give me all the news objects because it enters the same .getNews function on for both the urls.
My controller:
const NewsController = {};
const News = require('../models/news.model');
// This implementation of getNews is using Promises
NewsController.getNews = function(req, res) {
console.log('Inside getNews');
sendResponse = function(arg) {
res.json(arg);
}
const allnews = News.find({}, function(err, ns) {
sendResponse(ns);
});
};
// ES6 style
NewsController.getSingleNews = async (req, res) => {
console.log("Inside getSingleNews");
const news = await News.findById(req.params.id);
res.json[news];
};
NewsController.createNews = async (req, res) => {
const news = new News(req.body);
await news.save();
res.json[{
'status': 'item saved successfully'
}];
};
NewsController.deleteNews = async (req, res) => {
await News.findByIdAndRemove(req.params.id);
res.json[{
'status': 'item deleted successfully'
}]
};
module.exports = NewsController;
My routes.js (I am using the router at /api. So app.js has // use Router
app.use('/api', newsRoutes);
)
const express = require('express');
const router = express.Router();
var newsController = require('../controllers/NewsController')
router.get('/news', newsController.getNews);
router.get('/news/:id', newsController.getSingleNews);
router.post('/news', newsController.createNews);
router.delete('news/:id', newsController.deleteNews);
module.exports = router;
My Model
const mongoose = require('mongoose');
const { Schema } = mongoose;
const newsSchema = new Schema({
title: { type: String, required: true },
content: { type: String, required: true },
author: { type: String },
image: { type: String },
source: { type: String }
});
module.exports = mongoose.model('news', newsSchema);
The issue with your code is the way you are trying to call your endpoint. Express routes don't match query string parameters.
Having said that, your call to the news endpoint that looks like this:
http://localhost:5000/api/news/?id=c5f69d56be40e3b56e55d80
Should look like this instead:
http://localhost:5000/api/news/c5f69d56be40e3b56e55d80
That way the id parameter will get mapped to the req.params.id property inside your getSingleNews controller.
Being that the expected behavior for the way you declared your route:
router.get('/news/:id', newsController.getSingleNews);
For more information on how express routes work, check the documentation here: https://expressjs.com/en/guide/routing.html
Use /news/:id first. Your request will be redirected to the first matched url following the declaration order.
So /api/news satisfies app.get(/news)? Yep, gets redirected to that controller.
/api/news/?id=c5f69d56be40e3b56e55d80 satisfies app.get(/news)? Yep, also gets redirected to /news controller.
By the way, as your getting the id from req.params you should use /news/c5f69d56be40e3b56e55d80. If you were to get it from req.query you wouldn't need another route. /news?id=c5f69d56be40e3b56e55d80 would be perfect, you'd just need to check the existence of req.query.
I am trying to implement a mechanism that will be run before any route is hit. In that mechanism I want to take a value from the header and check for authentication.
I have come up with this:
server.js:
// Create a server with a host and port
'use strict';
var Hapi = require('hapi');
var mongojs = require('mongojs');
var plugins = [
require('./routes/entities')
];
var server = new Hapi.Server();
server.connection({
port: 3000
});
//Connect to db
server.app.db = mongojs('hapi-rest-mongo', ['entities']);
server.app.checkHeader = function (request) {
var header = request.headers['x-authorization'];
if(header === "letmein"){
return true
}
return false
};
//Load plugins and start server
server.register(plugins, function (err) {
if (err) {
throw err;
}
// Start the server
server.start(function (err) {
console.log('Server running at:', server.info.uri);
});
});
and in routes.entities:
'use strict';
var Boom = require('boom');
var uuid = require('node-uuid');
var Joi = require('joi');
exports.register = function (server, options, next) {
var db = server.app.db;
server.route({
method: 'GET',
path: '/entities',
handler: function handler(request, reply) {
if(!server.app.checkHeader(request))
{
return reply(Boom.unauthorized());
};
//request.server.myFunc();
db.entities.find(function (err, docs) {
if (err) {
return reply(Boom.wrap(err, 'Internal MongoDB error'));
}
reply(docs);
});
}
});
So in short while starting the server I have registered my function server.app.checkHeader
And in the routes I am calling it and sending a request object to it. Request object contains information about the headers.
While this works, I am having a feeling I am not following the best practices with the Hapi.
How could I do it more elegantly?
There are a few options.
You can, of course, tap into the request lifecycle - note the events that occur in the pipeline prior to the route handler.
Although, I'd urge you to consider implementing an auth strategy that can be set as the default for all routes or selectively on appropriate routes.
The best way to require authentication for all or selected route is to use hapi’s integrated functionality.
You should set a default authentication strategy that is applied to each route handler. The sample below uses basic auth. You’d want to create a custom authentication strategy for hapi to check your x-authentication header.
const Hapi = require('hapi')
const BasicAuth = require('hapi-auth-basic')
const server = new Hapi.Server()
server.register(BasicAuth, function (err) {
if (err) {
console.log('error', 'failed to install plugins')
throw err
}
// TODO: add authentication strategy & set as default
server.auth.strategy('simple', 'basic', true, { validateFunc: basicValidationFn })
// or set strategy separately as default auth strategy
server.auth.strategy('simple', 'basic', { validateFunc: basicValidationFn })
server.auth.default('simple')
// TODO: add routes
server.start(function (err) {
})
})
You can also inject hapi’s request lifecycle and extend it at given points. Extending the request lifecycle should be done by using plugins:
register: function (server, options, next) {
// do some processing before 'onPreAuth'
// or pick another extension point
server.ext('onPreAuth', (request, reply) => {
// your functionality
})
}
Hope that helps!
I have set up a hapi server as follows:
'use strict'
// node modules
const Hapi = require('hapi')
const Inert = require('inert')
const Path = require('path')
// server config
const server = new Hapi.Server()
const port = 4000
server.connection({
port: port
})
// Hapi plugins
const plugins = [
Inert
]
server.register(plugins, (err) => {
if (err) {
throw err
}
server.route([
{
method: 'GET',
path: '/',
handler: (request, reply) => {
reply.file(Path.join(__dirname, '../front/production/index.html'))
}
},
{
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: 'front/production'
}
}
}
])
})
module.exports = server
I have written tests for it, but have a problem being able to cover this line:
if (err) {
throw err <----
}
how can I mock a server error in server.register, so far I have tried:
server.inject({method: 'GET', url: '/notanendpoint'}...
server.inject({method: 'NOTMETHOD', url: '/'}...
server.inject({method: 'GET', url: '/', simulate: {error: true}}...
I've been looking through the hapi api docs and cant work out how to cover this line, any help would be greatly appreciated.
Let me know if I can add more description to my question or include what I have tried in my tests (let me know if you want to see more of my test file)
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
I´m trying associate a method or a property on a app object in kraken.js application, like follows:
controllers/index.js
'use strict';
var IndexModel = require('../models/index');
module.exports = function (app) {
var model = new IndexModel();
app.get('/', function (req, res) {
console.log(app.adventurer);
/* Console should be showing "Bilbo Bagins", but I'm getting 'undefined'.
* See the next source file. */
res.render('index', model);
});
};
/index.js
var kraken = require('kraken-js'),
app = {
adventurer: 'Bilbo Bagins'
};
app.configure = function configure(nconf, next) {
// Async method run on startup.
next(null);
};
app.requestStart = function requestStart(server) {
// Run before most express middleware has been registered.
};
app.requestBeforeRoute = function requestBeforeRoute(server) {
// Run before any routes have been added.
};
app.requestAfterRoute = function requestAfterRoute(server) {
// Run after all routes have been added.
};
if (require.main === module) {
kraken.create(app).listen(function (err) {
if (err) {
console.error(err.stack);
}
});
}
module.exports = app;
Also, i´ve tried publish the property on /config/app.json
Any Thoughts?
Simply add the following key to .config/app.json or make a new .config/app-development.json:
"adventurer": "bilbo"
app.json will look like this:
{
//blah
//blah
"adventurer": "bilbo"
}
and then in ./index.js do this in configure:
app.configure = function configure(nconf, next) {
// Async method run on startup.
next(null);
console.log('my traveler is: ', nconf.get('adventurer'));
};
In response to your comment, if you want to get an app config from the ./controllers/index.js then require the nconf lib and use nconf.get like so:
'use strict';
var nconf = require('nconf');
var IndexModel = require('../models/index');
module.exports = function (app) {
var model = new IndexModel();
//or attach it directly to the app object like so
app.set('adventurer', nconf.get('adventurer'));
console.log('adventurer directly set on app object', app.get('adventurer'));
console.log('controller with app adventurer:', nconf.get('adventurer'));
app.get('/', function (req, res) {
res.render('index', model);
});
};
Fire it up with npm start and watch the console. Peace!