Loopback: How to access a model from a script? - node.js

I am using Loopback and want to persist data to the database through a script.
I have written a custom command which I will be running through a cronjob:
'use strict';
var loopback = require('loopback');
var app = module.exports = loopback();
var boot = require('loopback-boot');
app.start = function() {
return app.listen(function() {
const baseUrl = app.get('url').replace(/\/$/, '');
console.log('Web server listening at: %s', baseUrl);
let Dish = app.models.dish;
console.log(Dish);
})
}
boot(app, __dirname, function(err) {
if (err) throw err;
// start the server if `$ node server.js`
if (require.main === module)
app.start();
});
The output I get is:
Web server listening at: http://localhost:3000
undefined
How do I access the dish model?

You're not calling the boot function
https://github.com/strongloop/loopback-boot
The loopback-boot module initializes (bootstraps) a LoopBack
application. Specifically, it:
Configures data-sources.
Defines custom models Configures models and attaches models to data-sources.
Configures application settings
Runs additional boot scripts, so you can put custom setup code in multiple small files instead of in the main application file.
Your server js likely contains something similar to this
var boot = require('loopback-boot');
app.start = function() {
return app.listen(function() {
const baseUrl = app.get('url').replace(/\/$/, '');
console.log('Web server listening at: %s', baseUrl);
if (app.get('loopback-component-explorer')) {
const explorerPath = app.get('loopback-component-explorer').mountPath;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
})
}
boot(app, __dirname, function(err) {
if (err) throw err;
// start the server if `$ node server.js`
if (require.main === module)
app.start();
});
You need these to init the app. You might be able to get away with only calling boot, but I think app.start is the one which gets your datasources connected.

Build your command script like this:
let app = require('./server/server') // Set the path according on the location of your command script
app.models.YOUR_MODEL // Access the model

You can access your models in loopback as below:
First of all require server.js file in your current file.
const app = require('YOUR_SERVERJS_FILE_PATH');
const MY_MODEL = app.models.YOUR_MODEL_NAME
Here, YOUR_MODEL_NAME will be as same as in name value of YOUR_MODEL.json file.
Hope you will get my point.
Thank you.

Related

Windows Node Service not listening on port

I am trying to write a node service that listens on a port and writes to the Event log, but I can't connect when I launch a browser at http://localhost:41414/?x=y Im not getting event records from the listener file (which launches the express server). Any idea what I'm doing wrong?
I created a service using node-windows: launch using (node mysvc.js --install 42424)
var Service = require('node-windows').Service;
//-----------------------------------------------init vars
// Create a new service object
var svc = new Service({
name:'My Listener v1.1',
script: require('path').join(__dirname,'listener.js '+port)
});
// Listen for the "install" event, which indicates the
// process is available as a service.
svc.on('install',function(){
svc.start();
});
// Listen for the "uninstall" event so we know when it's done.
svc.on('uninstall',function(){
console.log('Uninstall complete.');
console.log('The service exists: ',svc.exists);
});
//-------------------------------------------------------end init vars
var port=0;
if(process.argv[2]=="--uninstall")
svc.uninstall();
else if(process.argv[2]=="--install")
if(parseInt(process.argv[3])>0){
port=parseInt(process.argv[3])
svc.install();
}
else{
console.log('must pass --install or --uninstall flag')
return;
}
listener.js starts listening on the port:
const express = require('express');
var EventLogger = require('node-windows').EventLogger;
var log = new EventLogger('My Listener');
port=41414
app.get('/data', (req, res) => {
const jdata = req.params[0];
log.info(jdata+' WOOOOO!!!!');
})
log.log(`Service listening on port ${port}`);
app.listen(port);
you just missing this line in your listener.js
const app = express()

ExpressJs server along with command line interface

I'm developing a REST API server using express.js but I would like to allow the developer to issue command trough the command line.
It is a development server that read a config.json, create endpoint and store data in-memory.
I would like to give the developer the ability to reload the base data without restarting the express server.
Any idea?
I just thought of what seems to me like a more elegant solution to your problem that you may be interested in: having the server automatically reload the config.json file whenever it changes. This is trivial to implement in Node.js:
const fs = require("fs"),
path = require("path");
var filePath = path.join(__dirname, "config.json");
fs.watch(filePath, function(event) {
if (event === "change") {
var file = require(filePath); // require loads JSON as a JS object
// do something with the newly loaded file here
console.log("Detected change, config file automatically reloaded.");
}
});
Perhaps a better option would be to add a route to the app like so (since the server will be blocking, and therefore not allow any command-line input on the same process):
"use strict";
const http = require("http"),
path = require("path"),
express = require("express"),
PORT = 8000;
var app = express();
function loadConfigFile(callback) {
var configFile = false;
try { // load config file if it exists
configFile = require(path.join(__dirname, "config.json"));
} catch(e) { // ignore if the config file doesn't exist
if (e.code !== "MODULE_NOT_FOUND") {
throw e
}
}
callback(configFile); // argument will be false if file does not exist
}
function authenticateAdminUser(req) { return true; } // dummy logic
app.get("/admin/reload", function(req, res, next) {
if (!authenticateAdminUser(req)) {
let err = new Error("Forbidden");
err.StatusCode = 403;
return next(err);
}
loadConfigFile(function(file) {
if (file) {
// do something with the file here
}
res.send("Reloaded config file successfully.");
});
});
loadConfigFile(function(file) {
if (file) {
// do something with the file here
}
app.listen(PORT, function() {
console.log("Server started on port " + PORT + ".");
});
});

Using Node's Domains in Mean.js

I am trying to get my MEAN application ready for production. The application was built on the Mean.js boilerplate. From my understanding, MEAN.js uses Forever.js to restart the application after an error (although documentation on preparing Mean.js for production is severely lacking); however, it appears the suggested way to handle the application crashing is using Node's Domains in conjunction with clusters. Here are a few references:
This is from Node's webpage on the deprecated uncaughtException Event:
Note that uncaughtException is a very crude mechanism for exception handling.
Don't use it, use domains instead.
Node.js domains : https://nodejs.org/api/domain.html
\http://shapeshed.com/uncaught-exceptions-in-node/
etc.
Although I have found many suggestions for using domains, I have yet to find one that tells me what needs to be done to incorporate domains in an application, especially one that has already been developed.
The Questions
What do I need to do to integrate node domains into a Mean.js application? From what I have gathered (from the Node.js domains webpage and here), you would go into server.js in the root of the Mean.js project and do something similar to this:
var cluster = require('cluster');
var PORT = +process.env.PORT || 1337;
if (cluster.isMaster) {
//Fork the master as many times as required.
cluster.fork();
cluster.fork();
cluster.on('disconnect', function(worker) {
console.error('disconnect!');
cluster.fork();
});
} else {
var domain = require('domain');
var d = domain.create();
d.on('error', function(er) {
console.error('error', er.stack);
try {
// make sure we close down within 30 seconds
var killtimer = setTimeout(function() {
process.exit(1);
}, 30000);
// But don't keep the process open just for that!
killtimer.unref();
// stop taking new requests.
server.close();
// Let the master know we're dead. This will trigger a
// 'disconnect' in the cluster master, and then it will fork
// a new worker.
cluster.worker.disconnect();
// try to send an error to the request that triggered the problem
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Oops, there was a problem!\n');
} catch (er2) {
// oh well, not much we can do at this point.
console.error('Error sending 500!', er2.stack);
}
});
d.run(function() {
//Place the current contents of server.js here.
});
}
Do I need to wrap all of the backend controllers in domain.run()?
This answer was found by experimenting and a lot more digging. I had to edit both server.js and config/express.js to use domains. The domain is added part of the Express middleware for each request. Do not use the code in the question, it won't work as is.
First, the changes I made to server.js:
var init = require('./config/init')(),
config = require('./config/config'),
mongoose = require('mongoose'),
cluster = require('cluster');
var processes = 4; //Number of processes to run at the same time.
if(cluster.isMaster) {
for(var i = 0; i < processes; i++) {
cluster.fork();
}
cluster.on('disconnect', function(worker) {
console.error("Disconnect!");
cluster.fork();
});
} else {
/**
* Main application entry file.
* Please note that the order of loading is important.
*/
// Bootstrap db connection
var db = mongoose.connect(config.db, function(err) {
if (err) {
console.error('\x1b[31m', 'Could not connect to MongoDB!');
console.log(err);
}
});
// Init the express application
var expressConfig = require('./config/express');
var app = expressConfig.initialize(db);
app.use(function(err, req, res, next) {
console.error(err);
res.send(401).json({your_message_buddy: "Nice try, idiot."});
});
// Bootstrap passport config
require('./config/passport')();
// Start the app by listening on <port>
expressConfig.setServer(app.listen(config.port));
// Expose app
exports = module.exports = app;
// Logging initialization
console.log('MEAN.JS application started on port ' + config.port);
}
The necessary changes for config/express.js:
var domain = require('domain'),
cluster = require('cluster');
var appServer = null;
module.exports = {};
/**
* Since we begin listening for requests in server.js, we need a way to
* access the server returned from app.listen() if we want to close the
* server after an error. To accomplish this, I added this function to
* pass the server object after we begin listening.
*/
module.exports.setServer = function(server) {
appServer = server;
};
module.exports.initialize = function(db) {
//Initialize express app
var app = express();
//Globbing model files
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
require(path.resolve(modelPath));
});
//Set up domain for request BEFORE setting up any other middleware.
app.use(function(req, res, next) {
//Create domain for this request
var reqdomain = domain.create();
reqdomain.on('error', function(err) {
console.error('Error: ', err.stack);
try {
//Shut down the process within 30 seconds to avoid errors.
var killtimer = setTimeout(function() {
console.error("Failsafe shutdown.");
process.exit(1);
}, 30000);
//No need to let the process live just for the timer.
killtimer.unref();
//No more requests should be allowed for this process.
appServer.close();
//Tell master we have died so he can get another worker started.
if(cluster.worker) {
cluster.worker.disconnect();
}
//Send an error to the request that caused this failure.
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end('Oops, there was a problem. How embarrassing.');
} catch(err2) {
//Well, something is pretty screwed up. Not much we can do now.
console.error('Error sending 500!\nError2: ', err2.stack);
}
});
//Add request and response objects to domain.
reqdomain.add(req);
reqdomain.add(res);
//Execute the rest of the request chain in the domain.
reqdomain.run(next);
});
//The rest of this function, which used to be module.exports, is the same.
};

Setting up mongodb on Heroku with Node

On my local host, I have the following Node code to setup a mongoDB database name "dbname":
users.js:
var MongoClient = require("mongodb").MongoClient,
Connection = require("mongodb").Connection,
Server = require("mongodb").Server;
Users = function(host, port) {
var mongoClient = new MongoClient(new Server(host, port));
mongoClient.open(function (){});
this.db = mongoClient.db("dbname");
};
Users.prototype.getCollection = function (callback) {
this.db.collection("users", function (error, users) {
if (error) callback(error);
else callback(null, users);
});
};
Users.prototype.findAll = function (callback) {
this.getCollection(function (error, users) {
if (error) {
callback(error);
} else {
users.find().toArray(function (error, results) {
if (error) {
callback(error);
} else {
callback(null,results);
}
});
}
});
}
// Bunch of other prototype functions...
exports.Users = Users;
I like to put the above database functionality in one file, and then in my main server file require that file as follows:
server.js:
var Users = require("./users").Users;
var users = new Users("localhost", 27017);
users.findAll(function (err, user) {
// Do something
});
To have this working on localhost is pretty easy. In the command line, I just type the following:
$ mongod # to launch the database server
$ node server.js # to launch the web server
and it works fine. However, now I'm trying to push the whole thing onto Heroku with the mongolab addon
heroku addons:add mongolab
but the database is not running and I have no idea how to make it run. This tutorial explains how to setup mongodb with the mongolab URI, but that's not how my code works, I use a host and a port and I create a new server based on that. How should I change my code for it to work on the heroku app? I want to keep the database code in a separate file, with the prototype functions.
Follow the example here at the "MongoClient.connect" section.
Essentially, you will need to change this part of the code:
Users = function(host, port) {
var mongoClient = new MongoClient(new Server(host, port));
mongoClient.open(function (){});
this.db = mongoClient.db("dbname");
};
To use mongoClient.connect() instead of new MongoClient:
Users = function(url) {
MongoClient.connect(url, function(err, db) {
// Find better way to set this since this callback is asynchronous.
this.db = db;
});
};
If you are using node, I recommend using a library such as mongoose npm install mongoose to handle mongodb interactions. Look at my answer here for how to structure your schemas.
Helped by Xinzz's answer, here's the modified code, so that the mongodb database is initialized with a URI instead of host + port. That's how Heroku initializes the mongodb database, and that's why it wasn't working.
var mongodb = require("mongodb");
var MONGODB_URI = process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || "mongodb://localhost", // Make sure to replace that URI with the one provided by MongoLab
db,
users;
mongodb.MongoClient.connect(MONGODB_URI, function (err, database) {
if (err) throw err;
db = database;
users = db.collection("users");
accounts = db.collection("accounts");
var server = app.listen(process.env.PORT || 3000);
console.log("Express server started on port %s", server.address().port);
});
The key here is to declare the variables db and users upfront, assign them a value in the asynchronous callback of the connect function of MongoClient and also start the app (app.listen(...)) in the same callback. Then later in the code I can do the following:
users.find().toArray(function (err, results) {
// Do something
});
I also gave up on all these prototype functions, since they did not really add much.

Node.js calling function on child_process.fork

I have a server.js module that exports a start() function to start my server.
I require this module and start the server from index.js.
I'm trying to unit test the server.js module in isolation (with Mocha) by starting the server in a child_process.fork call but I don't see how to call the exported start function.
It's currently working by passing 'index.js' to the fork call but then I don't see how to pass options through to the server.js module (sending a port number for example).
Here's my server.js and the unit test that uses index.js (which only requires and calls server.start()).
I'd like to test server.js directly so I can pass environment variables to it.
====EDIT====
I'm not sure what I thought I would gain by starting the server in a separate process.
I've changed the test to just start the server in the before block.
Suggestions welcome.
var assert = require("assert");
var request = require("request");
describe("Server", function(){
var server;
var port = 4008;
before(function(done){
server = require("../server");
server.start(port);
done();
});
it('listens on specified port (' + port + ')', function(done){
request('http://localhost:' + port, function(err, res, body){
assert(res.statusCode == 200);
done();
});
});
});
You may want to use the cluster module for this, which makes handling processes a little simpler. The following may be along the lines of what you need.
var cluster = require('cluster');
// Runs only on the master process.
if (cluster.isMaster) {
var environmentVariables = { PORT: 2020 };
var worker = cluster.fork(environmentVariables);
// Catch a message from the worker, and then destroy it.
worker.on('message', function(message) {
if (message.hasOwnProperty('testResult')) {
// Kill the worker.
worker.destroy();
// Then do something with the result.
}
});
}
// Runs only on a worker process.
if (cluster.isWorker) {
var server = require('./server');
server.start();
// Do some stuff for tests.
// Send the test result.
process.send({ testResults: 'pass', failReason: null });
}
I haven't tested this, but hopefully the gist is clear. You can pass in custom environment variables when the worker process is spawned, and then have the worker process message the master with the result. You probably need to handle exit events and a time out for when the worker crashes or hangs up.
As an aside, you should probably be wrapping the process.env.PORT in a parseInt.

Resources