I am new to Hapi.js and I am stuck a point trying to figure out how can we load plugins in order in a Hapi.js setup.
For example: I have 2 plugins Plugin1 and Plugin2. Lets say Plugin2 is dependent on Plugin1 and cannot run until Plugin1 is executed.
It seems like loading these plugins in 2 separate server.register methods or with a single server.register (with array of plugins) seems to be executing the plugins code in parallel...
So, can some one help me with how can I load plugins in order...thanks in advance
You will want to use server.dependency as a solution.
With it, you can declare a plugin dependent upon another and if the dependency is missing (or you accidentally create a circular dependency) your server will throw.
With this, you have an opportunity to use the after function to delay execution of code in Plugin2 that must wait until Plugin1 is loaded.
You can also see discussion titled "Inconsistent plugin load order" on the glue github repo or this one called "Server start can fail due to plugins not loaded in the right order" from aqua for additional info and details.
You have a few options available to you.
You could take a look at Glue. You can use the array syntax for plugins to load plugins in a specific order:
var Glue = require('glue');
var manifest = {
server: {
cache: 'redis'
},
connections: [
{
port: 8000,
labels: ['web']
},
{
port: 8001,
labels: ['admin']
}
],
plugins: [
{ 'Plugin1': null },
{ 'Plugin2': null }
]
};
var options = {
relativeTo: __dirname
};
Glue.compose(manifest, options, function (err, server) {
if (err) {
throw err;
}
server.start(function () {
console.log('Hapi days!');
});
});
This is same as doing the following without the use of Glue:
server.register(require('Plugin1'), function (err) {
server.register(require('Plugin2'), function (err) {
server.start(function () {
console.log('Hapi days!');
});
});
});
Having order-dependent plugins is messy though and hapi offers a better way to fix this. You can use server.dependency() to explictly express a plugin's dependency on another plugin. So inside Plugin2 you could do:
var ready = function (server, next) {
server.route({
...
});
next();
};
exports.register = function (server, options, next) {
server.dependency('Plugin1', ready);
next();
};
exports.register.attributes = {
name: 'Plugin2',
version: '0.0.1'
};
With this approach, it doesn't matter about the plugin registration order. This is great for big apps where there's lot of plugins worked on by different people or teams.
You can register your plugins in order after you created your server instance.
For example:
const server = new Hapi.Server(ConfigUtils.resolve(Config.Server));
await server.register([
PluginUtils.bootstrap(AwsPlugin, ConfigUtils.resolve(Plugin.Aws) ),
PluginUtils.bootstrap(OrmPlugin, ConfigUtils.resolve(Plugin.Orm) ),
PluginUtils.bootstrap(SessionPlugin, ConfigUtils.resolve(Plugin.Session) ),
]);
Related
I have a nuxt application in which I will need to append data from a generated configuration file when the application is first started. The reason I cannot do this in the actual build is because the configuration file does not exists at this point; it is generated just before calling npm start by a bootstrap script.
Why don't I generated the configuration file before starting the application you may ask and this is because the application is run in a docker container and the built image cannot include environment specific configuration files since it should be used on different environments such as testing, staging and production.
Currently I am trying to use a hook to solve this, but I am not really sure on how to actually set the configuration data in the application so it can be used everywhere:
# part of nuxt.config.js
hooks: {
listen(server, listener) {
# load the custom configuration file.
fs.readFile('./config.json', (err, data) => {
let configData = JSON.parse(data));
});
}
},
The above hook is fired when the application first starts to listen for connecting clients. Not sure this is the best or even a possible way to go.
I also made an attempt of using a plugin to solve this:
import axios from ‘axios’;
export default function (ctx, inject) {
// server-side logic
if (ctx.isServer) {
// here I would like to simply use fs.readFile to load the configuration, but this is not working?
} else {
// client-side logic
axios.get(‘/config.json’)
.then((res) => {
inject(‘storeViews’, res.data);
});
}
};
In the above code I have problems both with using the fs module and axios.
I was also thinking about using a middleware to do this, but not sure on how to proceed.
If someone else has this kind of problem here is the solution I came up with in the end:
// plugins/config.js
class Settings
{
constructor (app, req) {
if (process.server) {
// Server side we load the file simply by using fs
const fs = require('fs');
this.json = fs.readFileSync('config.json');
} else {
// Client side we make a request to the server
fetch('/config')
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((json) => {
this.json = json;
});
}
}
}
export default function ({ req, app }, inject) {
inject('config', new Settings(app, req));
};
For this to work we need to use a server middleware:
// api/config.js
const fs = require('fs');
const express = require('express');
const app = express();
// Here we pick up requests to /config and reads and return the
// contents of the configuration file
app.get('/', (req, res) => {
fs.readFile('config.json', (err, contents) => {
if (err) {
throw err;
}
res.set('Content-Type', 'application/json');
res.end(contents);
});
});
module.exports = {
path: '/config',
handler: app
};
First of all, this is one of my first projects in Node.js so I'm very new to it.
I have a project I want to make that is a SOAP (I know, SOAP... backwards compatibility, huh?) interface that connects to an Oracle database.
So I have a WSDL describing what these functions look like (validation for addresses and stuff) and I have a connection to the database.
Now when using the SOAP npm module, you need to create a server and listen using a service that allows you to respond to requests. I have a separate file that contains my SOAP service but this service should do queries on the database to get its results.
How would I go about sort of 'injecting' my database service into my SOAP service so that whenever a SOAP call is done, it orchestrates this to the correct method in my database service?
This is what my code looks like:
databaseconnection.js
var oracledb = require('oracledb');
var dbConfig = require('../../config/development');
var setup = exports.setup = (callback) => {
oracledb.createPool (
{
user : dbConfig.user,
password : dbConfig.password,
connectString : dbConfig.connectString
},
function(err, pool)
{
if (err) { console.error(err.message); return; }
pool.getConnection (
function(err, connection)
{
if (err) {
console.error(err.message);
return callback(null);
}
return callback(connection);
}
);
}
);
};
databaseservice.js
var DatabaseService = function (connection) {
this.database = connection;
};
function doSomething(callback) {
if (!this.database) { console.log('Database not available.'); return; }
this.database.execute('SELECT * FROM HELP', function(err, result) {
callback(result);
});
};
module.exports = {
DatabaseService: DatabaseService,
doSomething: doSomething
};
soapservice.js
var myService = {
CVService: {
CVServicePort: {
countryvalidation: function (args, cb, soapHeader) {
console.log('Validating Country');
cb({
name: args
});
}
}
}
};
server.js
app.use(bodyParser.raw({type: function(){return true;}, limit: '5mb'}));
app.listen(8001, function(){
databaseconnection.setup((callback) => {
var temp = databaseservice.DatabaseService(callback);
soapservice.Init(temp);
var server = soap.listen(app, '/soapapi/*', soapservice.myService, xml);
databaseservice.doSomething((result) => {
console.log(result.rows.length, ' results.');
});
});
console.log('Server started');
});
How would I go about adding the databaseservice.doSomething() to the countryvalidation soap method instead of 'name: args'?
Also: I feel like the structure of my code is very, very messy. I tried finding some good examples on how to structure the code online but as for services and database connections + combining them, I didn't find much. Any comments on this structure are very welcome. I'm here to learn, after all.
Thank you
Dieter
The first thing I see that looks a little off is the databaseconnection.js. It should be creating the pool, but that's it. Generally speaking, a connection should be obtained from the pool when a request comes in and release when you're done using it to service that request.
Have a look at this post: https://jsao.io/2015/02/real-time-data-with-node-js-socket-io-and-oracle-database/ There are some sample apps you could have a look at that might help. Between the two demos, the "employees-cqn-demo" app is better organized.
Keep in mind that the post is a little dated now, we've made enhancements to the driver that make it easier to use now. It's on my list to do a post on how to build a RESTful API with Node.js and Oracle Database but I haven't had a chance to do it yet.
I've picked up Node.js recently. Before I jump to Express.js and other web frameworks, I learned few things about them but I wanted to try some API programming. I downloaded this module: https://www.npmjs.com/package/steamwebapi
Since I'm new to Node.js, I made new javascript file, app.js and my code is:
var SteamWebAPI = require('steamwebapi').SteamWebAPI;
SteamWebAPI.setAPIKey('My key is here');
SteamWebAPI.getRecentlyPlayedGames('76561198190043289', 5, function(response) {
console.log(response);
});
SteamWebAPI.getRecentlyPlayedGames('76561198190043289', 5, function(response) {
console.log(response);
});
'76561198190043289' is my steam 64 id. When I type: node app.js in terminal, I get:
{ response: { total_count: 1, games: [ [Object] ] } }
{ response: { total_count: 1, games: [ [Object] ] } }
How do I display my results, what I'm doing wrong?
You are not doing anything wrong. console.log makes the output more compact for the sake of readability. It can get very lengthy at times so it's probably a good thing.
It means that "[ [Object] ]" is actually an array of one or more games. You can try using
console.dir( response );
instead, or you can be more specific with log:
console.log( response.response.games );.
There are other ways around this as well if you can bother to search around. Converting to a string seems to be popular:
console.log( JSON.stringify( response, null, 4) );
Off topic...
I would also like to mention something else (since you are new) the one thing everyone must relearn coming to node from js. Calling functions like you do:
function1(..., callback);
function2(..., callback);
Node moves on to the second function immediately without waiting for the first callback to finish. So you have no idea of which of those functions will finish first. To force the order you would have to do this:
SteamWebAPI.getRecentlyPlayedGames('76561198190043289', 5, function(response) {
console.log(response);
SteamWebAPI.getRecentlyPlayedGames('76561198190043289', 5, function(response) {
console.log(response);
});
});
Your code will turn into the infamous callback hell. There is no avoiding it. It will happen sooner or later! To prevent that, learn how to use promises. You'll be much better off going from there!
Edit: Connect to angular
You need to create a server-backend of some kind. This is how it could look like using express (since you mention express):
var express = require('express');
var server = express();
var SteamWebAPI = require('steamwebapi').SteamWebAPI;
SteamWebAPI.setAPIKey('My key is here');
// define endpoints
server.get('/games', function (req, res) {
SteamWebAPI.getRecentlyPlayedGames('76561198190043289', 5, function(response) {
res.json(response.response.games);
});
});
// Start the server
server.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
Once it's running you can test that it works by browsing to http://example.com:3000/games
Then you call your endpoint from angular:
var app = angular.module("app", []);
app.controller("myCtrl", function($scope, $http){
$scope.games = [];
$http.get('http://example.com:3000/games').then(function(response){
// The response is seldom exactly what you expect. Check your browser console.
console.log(response);
$scope.games = response.data;
});
});
html
<body ng-app="app" ng-controller="myCtrl">
<div ng-repeat="game in games">{{ game }}</div>
</body>
All done.
Mind you, this is all shooting from the hip (no testing whatsoever) , so there are bound to be errors. But it should give you a starting point at least.
Coming from the world of express, if there was an error when compiling a Jade template, the error would output to the browser with details of the template error including line number. I would like to see this level of error detail in Hapi.js when the view template compiler encounters an error.
Instead, with Hapi.js, I receive a 500 Internal Server Error instead. The only output I see in the logs is the following:
150511/005652.910, [response], http://localhost:3000: get /abc123 {} 500 (24ms)
This is the basics of my setup.
var Hapi = require('hapi');
var server = new Hapi.Server();
server.connection({ port: 3000 });
server.views({
engines: { jade: require('jade') },
path: __dirname + '/views',
compileOptions: {
pretty: true,
debug: true,
compileDebug: true
}
});
server.route({
method: 'GET',
path: '/{name}',
handler: function (request, reply) {
reply.view('page', {name: request.params.name});
}
});
// I know putting my plugins in an array is not necessary but I like it.
var plugins = [{
register: Good,
options: {
reporters: [{
reporter: require('good-console'),
events: {
response: '*',
log: '*'
}
}]
}
}];
server.register(plugins, function (err) {
if (err) {
throw err; // something bad happened loading the plugin
}
server.start(function () {
server.log('info', 'Server running at: ' + server.info.uri);
});
});
Had the same problem this week too.
var server = new Hapi.Server({
debug: {
request: ['error']
}
});
It won't output to the client (you'll still get a 500 error), but the compilation error will show up in the terminal console.
One possible solution is to log on server 'request-error'. For instance:
server.on('request-error', function (request, err) {
//logs the object
server.log('error', err);
//logs the view compiler error line number and details
server.log('error', err.toString());
});
I would still prefer to see this in the browser (while in "development mode") in addition to the logs.
Because the rendering of the template happens after onPreResponse, it's not possible to catch the error at that point. A way of sending the error to the browser, albeit slightly hacky, is to do a dry run of the compilation inside an extension point, and then transmit the error to the browser at that point:
server.ext('onPreResponse', function (request, reply) {
var response = request.response;
if (response.variety === 'view') {
var source = response.source;
// Let's pre-render the template here and see if there's any errors
return server.render(source.template, source.context, function (err) {
if (err) {
return reply(err.message); // transmit the compile error to browser
}
reply.continue();
});
}
reply.continue();
});
Obviously this has a performance impact because you're rendering the view twice under normal conditions, so you'll want to disable it in production.
When a user double clicks on a file with an extension of cmf, I would like to automatically launch the Electron application that I've built. I've been searching around and I've seen several mentions of the electron-builder but no examples of how this can be used to create this association.
You want to look at the protocol functionality. I don't have enough experience with it to understand the finer points: like which app takes precedence if multiple app register the same protocol. Some of that might be user-defined.
const { app, protocol } = require('electron')
const path = require('path')
app.on('ready', () => {
protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7)
callback({ path: path.normalize(`${__dirname}/${url}`) })
}, (error) => {
if (error) console.error('Failed to register protocol')
})
})