
Development of client-server infrastructure in javascript (part 2 - server and hosting)

Development of client-server infrastructure in javascript (part 1 - client)
The server is written in nodejs using swagger-node-express. This gives the advantage of auto-documentation . I want to note that the server was created, rather, in support of the client part, so some checks and optimizations were deliberately omitted and left for the future.
The server consists of a set of modules. In the main file, all modules are initialized.
var express = require("express"),
swagger = require("./swagger"),
orm = require('./orm'),
auth = require('./auth'),
config = require('./config'),
static_files = require('./static');
var app = express();
app.use(express.bodyParser());
auth.init(app);
orm.init(app);
swagger(app);
static_files(app);
app.listen(config.listen, config.ipaddr);
The main modules. For authentication, an http header is used, its name can be set in the parameters. The session is stored in memcached, it's just a match api_key -> user_id. User Verification Code.
var client = new memcache.Client(cfg.memcache.port, cfg.memcache.host);
...
app.use(function(req, res, next){
client.get(
req.headers[cfg.header.toLowerCase()],
function(error, result){
if(result){
req.user = result;
}
next();
}
);
req.memcache = client;
});
To work with the database, node-orm2 is used. I note that in package.json add only the database driver that you will use.
Connection to the base and an example of a model description.
app.use(orm.express(config.db, {
define: function (db, models) {
db.define("users",
{
id : Number,
email : String,
password : String,
twitter : Number,
facebook : Number,
google : Number,
linkedin : String
},
{
validations: {
email: [
orm.enforce.unique("Email already taken!"),
orm.enforce.unique({ ignoreCase: true }),
orm.enforce.notEmptyString()
],
password: orm.enforce.notEmptyString()
},
id: "id",
autoFetch: false
}
);
var Conferences = db.define("conferences",
{
id : Number,
title : String,
description : String,
datetime : Date,
place : String,
location : String,
site : String,
logo : String,
facebook : String,
twitter : String,
telephone : String,
cost : String,
file : String
},{
id: "id",
autoFetch: false
}
);
var Decisions = db.define("decisions",
{
id : Number,
decision : ['go', 'not go', 'favorite'],
user : Number,
conference_id : Number
},{
id: "id",
cache: false,
autoFetch: false
}
);
Decisions.hasOne('conference', Conferences, {reverse: 'decision'});
}
}));
Locally and on my VPS, nginx is used for statics, but in the case of PaaS, nodejs itself also gives statics, so I created a separate handler for it. It is necessary to give documentation and the client himself.
var static_handler = express.static(__dirname + '/../static/');
app.get(/^\/static(\/.*)?$/, function(req, res, next) {
if (req.url === '/static') { // express static barfs on root url w/o trailing slash
res.writeHead(302, { 'Location' : req.url + '/' });
res.end();
return;
}
req.url = req.url.substr('/static'.length);
return static_handler(req, res, next);
});
var main_handler = express.static(__dirname + '/../client/www/');
app.get(/^\/(.*)$/, function(req, res, next) {
if(req.url == '/cordova.js'){
return res.send('');
}
if(!req.url){
req.url = 'index.html';
}
return main_handler(req, res, next);
});
The cordova.js file is created by phonegap, contains communication functions with hardware and other platform-specific features. A stub is simply given to the browser, so the client knows that he cannot use the OS functions.
Now you need to initialize the server itself.
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', cfg.header+', Content-Type');
res.header('Access-Control-Expose-Headers', cfg.header+', Content-Type');
if (req.method == 'OPTIONS') {
res.send(200);
}
else {
next();
}
});
swagger.setAppHandler(app);
swagger.addModels(models);
controllers.init(swagger);
swagger.configure(cfg.basePath, "0.1");
// Serve up swagger ui at /docs via static route
var docs_handler = express.static(__dirname + '/../documentation/swagger/');
app.get(/^\/docs(\/.*)?$/, function(req, res, next) {
if (req.url === '/docs') { // express static barfs on root url w/o trailing slash
res.writeHead(302, { 'Location' : req.url + '/' });
res.end();
return;
}
// take off leading /docs so that connect locates file correctly
req.url = req.url.substr('/docs'.length);
return docs_handler(req, res, next);
});
So that from the browser you can freely use the API, cross-origin resource sharing headers are given. Then the swag initialization. Here you need to pay attention to
swagger.addModels(models);
controllers.init(swagger);
These models that are given, it can be said, are analogous to ViewModel and do not belong to the models described in orm.js. In controllers.js action handlers are registered. Example.
swagger.addGET(conferences.get);
swagger.addGET(conferences.list);
swagger.addPOST(conferences.decision);
swagger.addDELETE(conferences.reject);
How action is described. I will write an example of a handler for receiving a conference by id.
var get = {
'spec': {
"description" : "Get conference by id",
"path" : "/conferences.{format}/{id}",
"notes" : "Get conference",
"summary" : "Get conference",
"method": "GET",
"responseClass" : "Conference",
"nickname" : "conference"
},
'action': function (req,res) {
if (!req.params.id) {
throw swagger.errors.invalid('id'); }
var id = parseInt(req.params.id);
req.db.models.conferences.get(id, function(err, conference){
if(err){
res.send(500, JSON.stringify({code: 500, header: 'Internal Server Error', message: JSON.stringify(err)}));
}else{
if(conference){
if(conference.file){
conference.file = '/static/' + conference.file;
}
if(req.user){
conference.getDecision({user: req.user}, function(err, decision){
if(err){
res.send(500, JSON.stringify(err));
}else{
conference.decision = decision.pop();
res.send(200, JSON.stringify(conference));
}
});
}else{
res.send(200, JSON.stringify(conference));
}
}else{
throw swagger.errors.notFound('conference');
}
}
});
}
};
The "spec" is mainly documentation information, but here is the "path", that is, the url by which express will call the handler. The main part is action. The presence of parameters and a query to the database are checked. Who does not like when callbacks grow in breadth, I will say right away that node-orm2 allows you to build chains of both request and handlers. If files are attached to the conference, a path is generated for them. If the user is logged in, his decision regarding the conference is sought. This is done to save http requests, but I’m honestly saying that I don’t know how to better and more correct from the point of view of REST: attach the has-one model to the parent or return id, and let the client send another request itself if necessary.
On this, I think, the description of working with swagger-node-express is finished. As soon as the project began to take on a finished look, I wondered where to place it. At first he lived on my VPS, but I’m constantly llaming everything there, so it was decided to put it on PaaS. Because clouds are fashionable and also fun. By the way, many cloud hosting companies offer free accounts, and many of them without time limits. Thus, you can freely host a dozen projects.
Although I describe openshift in this article, I myself do not relate to them in any way and the hosting procedures on other hosting services will be very similar (it comes down to git push). I chose this hosting by accident.
So, first you need to register if you have not already done so. Then create a project, their documentation describes it in detail. I especially want to draw your attention to some things. Firstly, if you are thinking of hosting a project in the cloud, it’s better to immediately choose where and sharpen the project for this particular hosting. This way you will get rid of redoing the project before the deployment, for example, I needed to rename the main file in server.js, add support for the statics of the client application and use an external memcached server.
I moved client / www to server / www and added the directory with statics attached to the conferences and swagger there. This is what the new static.js looks like.
var static_handler = express.static(__dirname + '/www/static/');
app.get(/^\/static(\/.*)?$/, function(req, res, next) {
if (req.url === '/static') { // express static barfs on root url w/o trailing slash
res.writeHead(302, { 'Location' : req.url + '/' });
res.end();
return;
}
// take off leading /docs so that connect locates file correctly
req.url = req.url.substr('/docs'.length);
return static_handler(req, res, next);
});
var main_handler = express.static(__dirname + '/www/');
app.get(/^\/(.*)$/, function(req, res, next) {
if(req.url == '/cordova.js'){
return res.send('');
}
if(!req.url){
req.url = 'index.html';
}
return main_handler(req, res, next);
});
Secondly, take an interest in SQL / no-SQL databases, because in some the databases are separate, and in some, openshift, for example, occupy a whole slot, and there are so few of them. I gave Memcached to another garantiadata.com service , where I also created a trial account. I still have 2 slots, by the way, yesterday the second instance of the node also started, which pleased me, because I was not sure that the auto-scaling would be successful.
And yes, the deployment itself.
git add --all; git commit -m 'commit'; git push origin master
Do not forget to add origin, which can be obtained on the application page in openshift. Also indicate to your application the correct host / port for the service.
exports.listen = process.env.OPENSHIFT_NODEJS_PORT || 8080;
exports.ipaddr = process.env.OPENSHIFT_NODEJS_IP || "127.0.0.1";
Status can be monitored , with git push you get a log. You can also go by ssh and see tail_all.
Through ssh, you can also connect to the database, after having looked at the connection string.
echo $OPENSHIFT_POSTGRESQL_DB_URL
That's all, the project in the cloud, everyone is happy. As always, I am glad to criticism and suggestions.