Multi environment Express Api - javascript

I am working on an Multi-Environment API based on Express framework. What i want is to keep my configuration dynamic, e.g. This Api would be able to serve both mobile apps and Web apps. If request is coming from mobile source than
config-app-1.json should be included, otherwise config-app-2.json.
Currently I have config-app-1.json, config-app-2.json, config-db-1.json, config-db-2.json and a configManager.js class which sets required configuration in app.listen(). In other application modules i require configManager and use the necessary configurations. This however leads to code duplication issue in individual functions. Each function has to get reference of db and application settings in its local scope.
I would like to know what are best practices for a multi-environment API build using Express framework.

These are configuration files, here is my approach.
File Structure
.
├── app.js
├── _configs
| ├── configManager.js
| ├── database.js
| └── platform
| ├── mobile.js
| └── desktop.js
Environment Configs
Configration files are js modules for each device, then the configManager handles which one is active based on device.
//mobile.js example
module.exports = {
device: 'mobile',
configVar: 3000,
urls: {
base: 'DEVICE_SPECIFIC_BASE_URL',
api: 'DEVICE_SPECIFIC_BASE_URL'
},
mixpanelKey: 'DEVICE_SPECIFIC_BASE_URL',
apiKey: "DEVICE_SPECIFIC_BASE_URL",
}
Database Config
Database configurations should be centralized.
Usually you can connect to multiple databases within the same node instance, however it is not recommended. if you absolutely have to, just use two objects (instead of "mongodb" replace with "mobileMongoDb" and "desktopMongoDb") but i recommend that you use one database and divide it into two main documents, or use certain prefixes set in your platform-specific configs.
// databse.js example
module.exports= {
mongodb: {
host : 'localhost',
port : 27017,
user : '',
password : '',
database : 'DB_NAME'
},
}
configManager.js (Putting things together)
This is a simple file for demonstration only..
var userAgent = req.headers['User-Agent'];
var isMobile = /Mobile|Android|/i.test(userAgent);
// require them all to be cached when you run node.
var configs = {
mobile: require('./platform/mobile' ),
desktop: require('./platform/desktop' )
}
var activeConfig = isMobile? configs.mobile : configs.desktop;
var dbConfigs = require('./databse');
var mongoose = require('mongoose');
var express = require('express');
var app = express();
app.get('/', function (req, res) {
var finalresp = 'Hello from ';
finalresp += isMobile? 'mobile' : 'desktop;
finalresp += activeConfig.configVar;
res.send(finalresp);
});
mongoose.connect(dbConfigs.mongodb.host, function(err) {
if(isMobile) { /* ... */ }
});
Detect Mobile from header
read more here https://gist.github.com/dalethedeveloper/1503252

You can set environment variables. What I usually do is have multiple config files as you have mentioned.
Then set environment variable NODE_ENV in local, development and production as "LOCAL", "DEVELOPMENT" and "PRODUCTION" respectively.
Then you can refer the environment by following code
ENV = process.env.NODE_ENV
if(ENV === 'PRODUCTION') {
mainConf = JSON.parse(fs.readFileSync(path.join(__dirname, '/config/main-production.json')))
dbConf = JSON.parse(fs.readFileSync(path.join(__dirname, '/config/db-production.json')))
} else if(ENV === 'DEVELOPMENT') {
mainConf = JSON.parse(fs.readFileSync(path.join(__dirname, '/config/main-development.json')))
dbConf = JSON.parse(fs.readFileSync(path.join(__dirname, '/config/db-development.json')))
} else if(ENV === 'LOCAL') {
mainConf = JSON.parse(fs.readFileSync(path.join(__dirname, '/config/main-local.json')))
dbConf = JSON.parse(fs.readFileSync(path.join(__dirname, '/config/db-local.json')))
}
Make sure you set environment variables properly on environment of each server.
Use the config json retrieved from the above code the way you want.

Can the source of the request (e.g. Mobile - Web) change during runtime? In other words can request 1 come from a mobile device and request 2 from the web?
If so, you could look at the user agent in the headers to determine the kind of device you're dealing with. This does make you dependent on the user agent though, and if it's not sent you won't have a way of identifying your client.
req.headers['User-Agent'];
If you own the clients yourself, you can add a property to every request, say an extra header. req.headers['X-Client-Type'] = 'Mobile'; //Web.
This way you aren't dependent on the user agent and still able to identify the type of each client.
Lastly, if you are dealing with third party clients, other people making applications to hit your API you might want to make them register their application. (Name, developer name, contact information, maybe agree to some type of service agreement, and also state the type of client, Web vs Mobile).
You'd then be able to fetch the type of each client on every new request.

Related

Node http-middleware-proxy and express against Tomcat

we are running our Java app (spring based) including the UI modules in a Tomcat container. Calling tomcat directly over http://localhost:8080 a login page is displayed and a redirect 302 occurs to the web app.
Now we want to develop the UI modules separately from the Java app by running an Express server with http-middleware-proxy and browser-sync. The modules have not been extracted out of the war-file and are running on the Tomcat instance. For testing purposes we just copied the UI module to another dir to setup Express and corresponding modules.
The problem is that we are not able to get the authorization cookies (JSESSIONID) and CSRF tokens correctly set.
How can the redirect 302 intercepted and redirected to the separately hosted UI app?
We´ve got the authorization working, so no login is required but calling the "copied app" does not work and results in "auth error" or "forbidden".
We already checked the documentation and other posts in here.
var cookie;
function relayRequestHeaders(proxyReq, req) {
if (cookie) {
proxyReq.setHeader('cookie', cookie);
}
};
function relayResponseHeaders(proxyRes, req, res) {
var proxyCookie = proxyRes.headers["set-cookie"];
if (proxyCookie) {
cookie = proxyCookie;
}
};
const oOptions = {
target: 'http://localhost:8080',
changeOrigin: true,
auth: 'user:pw',
pathRewrite: {
'^/dispatcher': '/dispatcher',
},
//cookieDomainRewrite: 'localhost',
onProxyReq: relayRequestHeaders,
onProxyRes: relayResponseHeaders,
logLevel: 'debug'
};
const wildcardProxy = createProxyMiddleware( oOptions );
app.use(wildcardProxy);
Any ideas on how to get that solved?
Thanks.
Update:
We tried as well to filter context paths which works but then it does not access the resources of the hosted webapp via express.
const oOptions = {
target: 'http://localhost:8080',
changeOrigin: true,
auth: 'user:pw',
logLevel: 'debug'
};
const wildcardProxy = createProxyMiddleware(['index.html', 'index.jsp', '!/resources/scripts/**/*.js'], oOptions );
app.use(wildcardProxy);
This is because we are proxying "/". How can it be achieved to only proxy the login and initial index.jsp but then using the resources of "webapp" and not of tomcat resources (e.g. *.js)? Is this possible somehow via the app.use to bind to certain files and paths?
We got that solved. We just excluded certain files from being served by the target host.
config.json:
{
...
"ignoreFilesProxy": ["!/**/*.js", "!/**/*.css", "!/**/*.xml", "!/**/*.properties"],
...
}
...
createProxyMiddleware(config.ignoreFilesProxy, oOptions);
...
Next step was to change the Maven build, so that the UI modules are not served/packaged with the war-file in the local dev environment. We solved that by introducing two Maven Profiles to include or exclude the UI modules in the WAR-project.

how to get a request in node repl

I've started to build a typescript library (intended to be used on the server side) and right now I'm trying to use the node repl to play around with my code and see what happens in certain situations... I've built and required the file, but now I'm having a problem: I have a function that takes a http Request (type Request from express.js), and I'd like to try and run it in the repl providing it with a copy of a request that I previously made from my browser. Is this feasible?
I thought maybe I could do it by either:
doing regex magic on the request exported as cURL or
sending the request to node, but then how am I going to receive it while in the repl?
I'm not sure I understand your use-case, but you can try something like this:
In some temp folder type:
npm install "request-promise"
Then from the same temp folder, enter the REPL and type:
(async () => {const response = await require("request-promise").get("https://cnn.com"); console.log(response)})()
This example is for get, but it can be easily changed to other HTTP methods.
I've found a fairly simple way to do what I want... It involved quickly setting up a basic express server (set up following this tutorial):
mkdir scratch && cd scratch && npm init
(select defaults except entrypoint app.js)
npm i express
Create an app.js (vi app.js) with the following contents:
var express = require('express');
var app = express();
var circ = {};
circ.circ = circ;
var cache = [];
app.get('/', function (req, res) {
res.send(JSON.stringify(req, (key, value) => {
if (typeof value === 'object' && value !== null) {
// Duplicate reference found, discard key
if (cache.includes(value)) return;
// Store value in our collection
cache.push(value);
}
return value;
}));
});
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
(See this answer for JSON.stringify custom replacer resp. second argument to JSON.stringify). You can optionally use flatted instead, which I discovered later and is surely better.
Now do the following:
Run the app with node app.js
In your browser, navigate to the website where your desired request is posted to.
Open your browsers development tools (Ctrl+shift+c works for me).
Go to the network tab.
Find the request that interests you and right click on it.
Click on copy > copy as curl (or similar, depending on which browser you're using).
Run that curl request, but change the url it's posted to to 127.0.0.1:3000 (e.g. change curl 'example.com' \...etc to curl '127.0.0.1:3000' \...etc
You should now get that request on standard output as a JSON object and it's in the format that express usually deals with. Yay! Now, pipe it into your clipboard (likely xclip -selection c on linux) or probably even better, redirect it to a file.
...
Step 2 - ?
Step 3 - Profit :)

Apple SignIn with Parse Server

My App is hosted on sashido.io, which offers Parse Server Hosting. Since it is required, I am trying to implement Apple SignIn for my app. As a first step, I've added the following code to my app. The Apple SignIn works, I get the token and the id, but I cannot create a Parse User with this data. This is my iOS-Code:
var authData = [String: String]()
authData["id"] = id
authData["token"] = token
PFUser.logInWithAuthType(inBackground: "apple", authData: authData).continueWith(block: {
task -> Void in
...
})
I have verified that the authData contains the id and the token properly. Unfortunately, I get an internal server error as response {"code":1,"message":"Internal server error."}
After that, I have modified the following part of my index.js file like this and pushed it to sashido. Unfortunately, this didn't change anything and I'm still getting the internal server error.
var api = new ParseServer(
{
databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
appId: process.env.APP_ID || 'myAppId',
masterKey: process.env.MASTER_KEY || 'masterKey',
serverURL: process.env.SERVER_URL || 'http://localhost:' + port + '/1',
// If you change the cloud/main.js to another path
// it wouldn't work on SashiDo :( ... so Don't change this.
cloud: process.env.CLOUD_CODE_MAIN || 'cloud/main.js',
auth: {
apple: {
client_id: process.env.IOS_BUNDLE_ID
}
},
liveQuery: {
classNames: []
},
});
Sign in with Apple support was initially released in Parse Server 3.5.0. However significant improvements and bug fixes have been made subsequently in 3.7.0, 3.8.0 and 4.2.0.
The latest version supported by Sashido is 3.6.0, however they haven't added support for Sign in with Apple yet. See the screenshot below from app settings > users > social login on 3.6.0...
Please also be aware that editing your index.js file and pushing it to your private Sashido GitHub repo will not change the Parse Server config. This facility is purely intended for local development use.
I would suggest you contact Sashido and ask them to add support for Sign in with Apple - ideally on Parse Server 4.2.0 this way you will get the latest improvements.

JS require in QML

I am using the dbox library in a QML app (sources available at github). In a QML file I import the dbox library with the following code:
import "./dbox-master/lib/dbox.js" as Dbox
Then I use it in this way:
var app = Dbox.app({ "app_key": root.appKey, "app_secret": root.appSecret })
However, in the dbox.js there're series of require statements, at the top of the file:
define(['require', 'request', 'querystring', 'path'], function (require) {
var request = require('request');
var qs = require('querystring');
var path = require('path');
var helpers_ = require("./helpers")
// var request = require('request');
});
//var request = require("request")
//var qs = require("querystring")
//var path = require("path")
//require(['request'], function (foo) {
// console.log('request is loaded')
//});
exports.app = function(config){
var root = config.root || "sandbox"
var helpers = helpers_(config)
return {
root: root,
requesttoken: function(cb){
var signature = helpers.sign({})
var body = qs.stringify(signature)
var args = {
"method": "POST",
"headers": {
"content-type": "application/x-www-form-urlencoded",
"content-length": body.length
},
"url": "https://api.dropbox.com/1/oauth/request_token",
"body": body
}
return request(args, function(e, r, b){
var obj = qs.parse(b)
obj.authorize_url = "https://www.dropbox.com/1/oauth/authorize?oauth_token=" + obj.oauth_token
cb(e ? null : r.statusCode, obj)
})
},...
As you see, I've changed the code to get the dbox.js to work but the require is not defined. How to use the require.js properly?
Update.
As I found out, the problem is in the host environment. The QML global space is constant. Node.js requires objects to be present in the space (e.g. iself) and importing into the global space. There is a project on github glueing Node.js and QML but it is not finished yet. I propose another solution: make a C++ plugin to run a script in js. The script is run in the Node.js environment to communicate Dropbox account information to the Quick-based application.
What is the best approach to use a web API? Dropbox in this case.
It depends.
Since I need to list all files and download a file, the best choice is to use the Dropbox API manually.
I used OAuth2 to authorize the application, the Token Flaw, because it is fast.
WebView was the app. built-in browser. Once a redirect_uri is set as the WebView url property value then it means the authorization is passed. The access token is returned with the redirection.
Note.
The redirect_uri must be equal to the redirect_uri set in the application AppConsole in the app. Dropbox account.
Result: I did not use the 3d party Dropbox JS libraries, it is much faster to send requests manually.

Using NodeJS with Express 3.x and Jade templates is it possible to just re-render one item for a previously rendered list?

I have been trying to find any post that can explain if it is possible to re-render one 'new' item (append) to a jade template list.
Say that we have a list of log-entries and upon first request we render a fetched list from a MongoDB collection 'logs', using res.render and Jades each functionality.
Since we like to retrieve updates from the database we also have a MongoWatch attached to that collection that listens for changes. Upon update can we execute some code that appends to that first list in the Jade-template?
/* app.js */
/*
Display server log
*/
app.get ('/logs', function(req, res, next) {
// Using Monk to retrieve data from mongo
var collection = db.get('logs');
collection.find({}, function(e,docs){
// watch the collection
watcher.watch('application.logs', function(event){
// Code that update the logs list with the new single entry event.data?
});
// Request resources to render
res.render('logs', { logs: docs } );
});
});
<!-- logs.jade -->
extends layout
block content
div
each log in logs
div.entry
p.url= log.url
Maybe i should use the template engine in another fashion, i am quite new to Express, Jade and really appreciate all you guys that spends your time answering problems like these..
// Regards
Ok, so i have looked up the suggestion from Jonathan Lenowski, thanks by the way!, and i came up with a solution to my problem. Thought i'd follow up and perhaps help someone else along the way..
Basically i am now using as suggested socket.io
So first install the socket.io npm module by adding it to package.json and run npm install, i used 'latest' as version.
Next to use the 'socket.io.js' on the client-side you actually have to copy the file from the installed socket.io module to your javascript folder.
Path (seen from project root is): 'node_modules/socket.io/node_modules/socket.io-client/dist/'
Setup DB, Watcher, Webserver, Socket and controller on server-side
/*
SETUP DATABASE HANDLE
in app.js
*/
var mongo = require('mongodb');
var monk = require('monk');
var db = monk('localhost:'+app.get('port')+'/application');
/* SETUP DATABASE UPDATE WATCH */
var watcher = new MongoWatch({ format: 'pretty', host: 'localhost', port: app.get('port') });
/* START WEBSERVER AND SETUP WEBSOCKET */
var server = Https.createServer({key: certData.serviceKey, cert: certData.certificate}, app);
var io = require('socket.io').listen(server);
server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
/*
Display server log - controller
*/
app.get ('/logs', function(req, res, next) {
// Using Monk to retrieve data from mongo
var collection = db.get('logs');
collection.find({}, function(e,docs){
// watch the collection logs in database application
watcher.watch('application.logs', function(event){
io.sockets.emit('logs', { log: event.data });
});
// Request resources to render
res.render('logs', { logs: docs } );
});
});
Include the socket.io javascript in layout
/*
Add client side script
in layout.jade
*/
script(type='text/javascript' src='/javascripts/socket.io.js')
Use the client
/*
SETUP DATABASE HANDLE
in logs.jade
*/
extends layout
block content
script.
var socket = io.connect('https://localhost:4431');
socket.on('logs', function (data) {
console.log(data.log);
// Here we use javascript to add a .log-entry to the list
// This minor detail i leave to the developers own choice of tools
});
div.row#logs
div.col-sm-12
div.header-log Some application
div.logs-section
each log in logs
div.log-entry.col-sm-12(data-hook=log.status)
p.method= log.method
p.url= log.url
p.status(style='color: #'+log.color+' !important')= log.status
p.response-time= log.time
p.content-length= log.length
p.datetime= log.date
Use the functionality, remember that this flow is triggered by actually adding a row in the database 'application' and the collection 'logs'.
I use ssl thus with regular http we create a 'http' server instead and connect from the client with a standard address prefix of http://...
Also as an additional note, in order to use MongoWatch it is required of you to setup the MongoDB with replication set. Which is a mirror database that can be used as a fallback (dual purpose).
Cheers! And once again thanks to Jonathan!

Categories

Resources