diff --git a/config/dev.json b/config/dev.json
new file mode 100644
index 0000000000000000000000000000000000000000..ee7d1a64f53c4e292b868806310a2b87f35bbf90
--- /dev/null
+++ b/config/dev.json
@@ -0,0 +1,8 @@
+{  
+    "options": {
+        "host": "bristol.api.urbanthings.io",
+        "headers": { "X-Api-Key": "0r6beKZn90a2c9EBs1IqA" },
+        "accept": "application/json"
+    },
+    "port": 8080
+}
diff --git a/config/development.json b/config/development.json
deleted file mode 100644
index 269e683d38d9d13b9be26050d9dd8f7eb1cf939c..0000000000000000000000000000000000000000
--- a/config/development.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-    "mysql" : {
-        "host"     : "localhost",
-	    "user"     : "root",
-	    "password" : "",
-	    "database" : "ATCO_010_BUS"
-    },
-    "bristolapi" : {
-        "rate": 2,
-        "options": {
-            "host": "bristol.api.urbanthings.io",
-            "headers": { "X-Api-Key": "0r6beKZn90a2c9EBs1IqA" },
-            "accept": "application/json"
-        }
-    }
-}
\ No newline at end of file
diff --git a/server.js b/server.js
index 5b692dfec6755122c4238d7cac889ca6fcf3a1cd..4366f42634851e89791d69362a81df3fe7411ad8 100644
--- a/server.js
+++ b/server.js
@@ -1,61 +1,70 @@
 // server.js
-// load the things we need
 
 // see openshift blog
 // https://blog.openshift.com/run-your-nodejs-projects-on-openshift-in-two-simple-steps/
 // use PORT environment variable or default 8080
-//var port = process.env.PORT || 8080;
 var port = process.env.OPENSHIFT_NODEJS_PORT || 8080
-// var server_ip_address = process.env.OPENSHIFT_NODEJS_IP || '127.0.0.1'
+
+// load the things we need
 
 var express = require('express');
-var ws = require('./ws');
+var ws = require('./wslib');
+exports.ws = ws;
 
-var app = express();
+var conf = require('./config/dev.json');
+var options = conf.options;
+exports.options = options;
 
+var app = express();
 // set the view engine to ejs
 app.set('view engine', 'ejs');
+exports.app = app;
 
-var errorFun = function (error) {
-    console.error("ERROR:", error);
-};
+// embed path p for ws invocation
+function path(p) {
+    options.path = p;
+    return options;
+}
+exports.path = path;
 
 // use res.render to load up an ejs view file
-//app.get('/', function(req, res) {
-//    res.render('pages/index');
-//});
+app.get('/', function(request, response) {
+   response.render('index');
+});
 
 // https://bristol.api.urbanthings.io/api/2.0/static/importsources
 
-app.get("/", function (ignore, res) {
-    ws.invoke("/api/2.0/static/importsources")
-        .then(function (results) {
-            // res.send(JSON.stringify(results));
-            res.render('pages/page1', results);
-        }, errorFun);
+app.get("/page1", function (request, response) {
+    ws.invoke(path("/api/2.0/static/importsources"), 
+    (err,data) => {
+        if (err) response.status(404).send("Oops!");
+        else response.render('page1', data);
+    }) ;
 });
 
 // https://bristol.api.urbanthings.io/api/2.0/static/agencies?importsource=TNDS
 
-app.get("/page2", function (req, res) {
-    "use strict";
-    var importsource = req.query.importsource;
-    ws.invoke("/api/2.0/static/agencies?importsource=" + importsource)
-        .then(function (results) {
-            res.render('pages/page2', results);
-        }, errorFun);
+app.get("/page2", function (request, response) {
+    var importsource = request.query.importsource;
+    ws.invoke(path("/api/2.0/static/agencies?importsource=" + importsource), 
+    (err,data) => {
+        if (err) response.status(404).send("Oops!");
+        else response.render('page2', data);
+    }) ;
 });
 
 // https://bristol.api.urbanthings.io/api/2.0/static/routes/info/Source?agencyID=UK_TNDS_NOC_FBRI
 
-app.get("/page3", function (req, res) {
-    "use strict";
-    var agencyID = req.query.agencyID;
-    ws.invoke("/api/2.0/static/routes/info/Source?agencyID=" + agencyID)
-        .then(function (results) {
-            res.render('pages/page3', results);
-        }, errorFun);
+app.get("/page3", function (request, response) {
+    var agencyID = request.query.agencyID;
+    ws.invoke(path("/api/2.0/static/routes/info/Source?agencyID=" + agencyID), 
+    (err,data) => {
+        if (err) response.status(404).send("Oops!");
+        else response.render('page3', data);
+    }) ;
 });
 
-app.listen(port);
-console.log('Listening on port '+port);
\ No newline at end of file
+if (process.env.NODE_ENV!="test") {
+    app.listen(port);
+    console.log('Listening on port '+port);
+}
\ No newline at end of file
diff --git a/views/pages/index.ejs b/views/index.ejs
similarity index 100%
rename from views/pages/index.ejs
rename to views/index.ejs
diff --git a/views/pages/page1.ejs b/views/page1.ejs
similarity index 100%
rename from views/pages/page1.ejs
rename to views/page1.ejs
diff --git a/views/pages/page2.ejs b/views/page2.ejs
similarity index 100%
rename from views/pages/page2.ejs
rename to views/page2.ejs
diff --git a/views/pages/page3.ejs b/views/page3.ejs
similarity index 100%
rename from views/pages/page3.ejs
rename to views/page3.ejs
diff --git a/wslib.js b/wslib.js
new file mode 100644
index 0000000000000000000000000000000000000000..d773692ce292112122ebe389a92484b9d431908f
--- /dev/null
+++ b/wslib.js
@@ -0,0 +1,30 @@
+/*
+Author: steve.battle@uwe.ac.uk
+Description: Web-Service client library
+*/
+
+var https = require('https');
+
+// rate limiter to avoid "API calls quota exceeded! maximum admitted per Second."
+var RateLimiter = require('limiter').RateLimiter;
+var limiter = new RateLimiter(1, 'second');
+
+function invoke(options, errback) {
+    limiter.removeTokens(1, () => {
+        try {
+            // asynchronous GET
+            https.get(options, response => {
+                // initialise streamed response
+                var body = "";
+                // add event listeners to response
+                response.on('data', d => body += d);
+                response.on('end', () => {
+                    errback(null, JSON.parse(body));        
+                }); 
+            });
+        }
+        catch (error) { errback(error); }
+    })
+}
+
+exports.invoke = invoke;
\ No newline at end of file