diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000000000000000000000000000000000000..834a48a40945a182db82602de135718f81d471b6
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1 @@
+{ 'presets': ['es2015'] }
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..ca947a13e5b915ed936efcd6a255b28d40501862
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+[
+  "**/test/*.js"
+]
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..14152dfcb9ae24e77005322efbbfbe811504ae58
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,14 @@
+{
+    "extends": "airbnb",
+    "root": true,
+    "plugins": [
+        "react",
+        "jsx-a11y",
+        "import"
+    ],
+    "rules": {
+        "indent": [ "error", 4 ],
+        "no-unused-vars": [ "error", { "args": "none" }],
+        "no-param-reassign": [ "off" ]
+    }
+}
diff --git a/.gitignore b/.gitignore
index 1be0326ac9a0235b55fa03f244f261b16eed1832..aba9677483cfad1c166ca57ec84c8fd263bbb56a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,8 +10,10 @@ lib-cov
 *.gz
 
 pids
-logs
 results
 
 npm-debug.log
 node_modules/
+build/*
+
+.gulp-cache
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3171c9f7e5653d1e4edbfd1064779609693773c3..b3949c4f704e19351c9126df4c326bdfaa229998 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,11 +2,12 @@ stages:
   - test
 
 before_script:
+  - npm install --global gulp gulp-cli babel babel-cli babel-core babel-register mocha
   - npm install
 
 run_tests:
   stage: test
   script:
-    - npm test
+    - gulp test
   tags:
     - node
diff --git a/README.md b/README.md
index 0c36372077dc52f20e792ebe2e323236d109dbc5..41202e148f16e12cd9c3e6996b158f3bae87e0b7 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,25 @@
 # SIMCAQ
+
+# Dependencies
+
+Previous versions of Node.js do not support ECMAScript6, it is recommended to use at least version 4.5.0LTS.
+
+1) Install [NVM (Node Version Manager)](https://github.com/creationix/nvm)
+
+2) Install Node.js via NVM
+
+> source ~/.bashrc
+
+> nvm install v4.5.0
+
+3) Enable Node.js
+
+> nvm use v4.5.0
+
+4) Install babel and gulp globally
+
+> npm install -g gulp gulp-cli babel babel-cli babel-core babel-register mocha
+
+5) Install project dependencies
+
+> npm install
diff --git a/config.json b/config.json
index 8f60036adf20a0512e709e683f876de75124d714..0574d6e9cc2246ce45b4af102c92b4a082f42bcd 100644
--- a/config.json
+++ b/config.json
@@ -1,11 +1,13 @@
 {
     "port": 3000,
+    "debug" : false,
     "monetdb": {
         "host": "simcaqdb1",
         "port": 50000,
         "dbname": "simcaq_dev",
         "user": "monetdb",
-        "password":"monetdb"
+        "password":"monetdb",
+        "nrConnections": "16"
     },
     "default": {
         "api": {
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
new file mode 100644
index 0000000000000000000000000000000000000000..cecae587a2e22e11052da3dc01371310086a74bd
--- /dev/null
+++ b/gulpfile.babel.js
@@ -0,0 +1,72 @@
+const gulp = require('gulp');
+
+const babel = require('gulp-babel');
+
+const eslint = require('gulp-eslint');
+
+const mocha = require('gulp-mocha');
+
+const nodemon = require('gulp-nodemon');
+
+const Cache = require('gulp-file-cache');
+
+const cache = new Cache();
+
+/**
+ * Compile source files
+ */
+function compile() {
+    // run ESLint
+    gulp.src('src/**/*.js')
+        .pipe(eslint())
+        .pipe(eslint.format());
+
+    // compile source to ES5
+    gulp.src('src/**/*.js')
+        .pipe(cache.filter())       // cache source files
+        .pipe(babel())      // compile only modified files
+        .pipe(cache.cache())        // cache compiled files
+        .pipe(gulp.dest('build'));  // move compiled files to build directory
+
+    // copy configuration file to build directory
+    gulp.src('config.json')
+        .pipe(gulp.dest('build'));
+
+}
+
+gulp.task('build', compile);
+
+gulp.task('test', () => {
+  gulp.src('test/test.js', {read: false})
+    .pipe(mocha())
+    .once('error', () => {
+      process.exit(1);
+    })
+    .once('end', () => {
+      process.exit();
+    });
+});
+
+gulp.task('watch', [], () => {
+    console.log('Watching source directory for changes');
+    compile();
+    gulp.watch('src/**/*.js').on('change', () => {
+        console.log('Recompiling source');
+        compile();
+        console.log('Source recompilation done')
+    });
+});
+
+gulp.task('run', () => {
+  process.chdir('build');
+  nodemon({
+    script: 'server.js',
+    tasks: ['watch'],
+    ignore: ["test/test.js", "gulpfile.babel.js"],
+    ext: 'js html json',
+    env: { 'NODE_ENV': 'development' }
+  });
+});
+
+gulp.task('default', ['run']);
+
diff --git a/libs/app.js b/libs/app.js
deleted file mode 100644
index 3fce3744a734395850a3fb02304c1c8a8881f5bc..0000000000000000000000000000000000000000
--- a/libs/app.js
+++ /dev/null
@@ -1,55 +0,0 @@
-var express = require('express')
-var path = require('path')
-var cookieParser = require('cookie-parser')
-var bodyParser = require('body-parser')
-var csv = require('csv-express')
-var xml = require('js2xmlparser')
-var methodOverride = require('method-override')
-var cors = require('cors')
-var apicache = require('apicache').options({ debug: true }).middleware
-
-var libs = process.cwd() + '/libs/'
-
-var config = require('./config')
-var log = require('./log')(module)
-
-var api = require('./routes/api')
-var states = require('./routes/states')
-var regions = require('./routes/regions')
-var cities = require('./routes/cities')
-
-var app = express()
-
-app.use(bodyParser.json())
-app.use(bodyParser.urlencoded({ extended: false }))
-app.use(cookieParser())
-app.use(cors())
-app.use(methodOverride())
-app.use(apicache('1 hour'))
-
-app.use('/api/v1/', api)
-app.use('/api/v1/states', states)
-app.use('/api/v1/regions', regions)
-app.use('/api/v1/cities', cities)
-
-// catch 404 and forward to error handler
-app.use(function(req, res, next){
-    res.status(404)
-    log.debug('%s %d %s', req.method, res.statusCode, req.url)
-    res.json({
-    	error: 'Not found'
-    })
-    return
-})
-
-// error handlers
-app.use(function(err, req, res, next){
-    res.status(err.status || 500)
-    log.error('%s %d %s', req.method, res.statusCode, err.message)
-    res.json({
-    	error: err.message
-    })
-    return
-})
-
-module.exports = app
diff --git a/libs/config.js b/libs/config.js
deleted file mode 100644
index 78f7831a7b1bb614fcebd8de04be18ff447d84a1..0000000000000000000000000000000000000000
--- a/libs/config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-var nconf = require('nconf')
-
-nconf.argv()
-	.env()
-	.file({
-		file: process.cwd() + '/config.json'
-	})
-
-module.exports = nconf
diff --git a/libs/db/monet.js b/libs/db/monet.js
deleted file mode 100644
index 0b554285d1b9d889ca1f40f7607672490eda73b3..0000000000000000000000000000000000000000
--- a/libs/db/monet.js
+++ /dev/null
@@ -1,19 +0,0 @@
-var mdb = require('monetdb')()
-
-var libs = process.cwd() + '/libs/'
-
-var log = require(libs + 'log')(module)
-var config = require(libs + 'config')
-
-var options = {
-    host: config.get('monetdb:host'),
-    port: config.get('monetdb:port'),
-    dbname: config.get('monetdb:dbname'),
-    user: config.get('monetdb:user'),
-    password: config.get('monetdb:password')
-}
-
-var conn = new mdb(options)
-conn.connect()
-
-module.exports = conn
diff --git a/libs/log.js b/libs/log.js
deleted file mode 100644
index 419b3e4b86d5d1e5a8e17858882c905204bab079..0000000000000000000000000000000000000000
--- a/libs/log.js
+++ /dev/null
@@ -1,35 +0,0 @@
-var winston = require('winston')
-
-winston.emitErrs = true
-
-function logger(module) {
-
-    return new winston.Logger({
-        transports : [
-            new winston.transports.File({
-                level: 'info',
-                filename: process.cwd() + '/logs/all.log',
-                handleException: true,
-                json: false,
-                maxSize: 5242880, //5mb
-                maxFiles: 2,
-                colorize: false
-            }),
-            new winston.transports.Console({
-                level: 'debug',
-                label: getFilePath(module),
-                handleException: true,
-                json: true,
-                colorize: true
-            })
-        ],
-        exitOnError: false
-    })
-}
-
-function getFilePath (module ) {
-    //using filename in log statements
-    return module.filename.split('/').slice(-2).join('/')
-}
-
-module.exports = logger
diff --git a/libs/middlewares/dimensions.js b/libs/middlewares/dimensions.js
deleted file mode 100644
index c17e4a6d2e00c1e5dc47455f4959cf9f33a0ad09..0000000000000000000000000000000000000000
--- a/libs/middlewares/dimensions.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
-* Dimensions middleware
-*
-* EXAMPLE:
-* Use it with no parameters to get all the dimensions specified
-* app.get('/', dimensions(), function(req, res, next){})
-*
-* Use it with an array of accepted values
-* app.get('/', dimensions(['year', 'location']), function(req, res, next){})
-*
-* Use it globally
-* app.use(dimensions())
-*/
-
-function intersect(a, b) {
-    var t
-    if (b.length > a.length) t = b, b = a, a = t
-    return a.filter(function (e) {
-        if (b.indexOf(e) !== -1) return true
-    })
-}
-
-function dimensions(dims) {
-    return function(req, res, next) {
-        req.dims = {}
-        if(req.query.dims) {
-            var params = req.query.dims.split(",")
-            var dimObj = {}
-            for(var i=0; i<params.length; ++i) {
-                var kv = params[i].split(":")
-                dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1]
-            }
-            // If the dims array exists and is not empty
-            if(typeof dims !== 'undefined' && dims.length > 0) {
-                var intersection = intersect(dims, Object.keys(dimObj))
-                for(var i=0; i<intersection.length; ++i) {
-                    req.dims[intersection[i]] = dimObj[intersection[i]]
-                }
-            } else {
-                req.dims = dimObj
-            }
-        }
-        console.log(req.dims)
-        next()
-    }
-}
-
-module.exports = dimensions
diff --git a/libs/middlewares/query.js b/libs/middlewares/query.js
deleted file mode 100644
index d14958dc43f6610cb3aa502ec6491db6a8095899..0000000000000000000000000000000000000000
--- a/libs/middlewares/query.js
+++ /dev/null
@@ -1,16 +0,0 @@
-var libs = process.cwd() + '/libs/'
-var conn = require(libs + 'db/monet')
-var log = require(libs + 'log')(module)
-
-function query(req, res, next) {
-    log.debug(req.query)
-    conn.query(
-        req.query.text, req.query.values, true
-    ).then(function(result) {
-        // log.debug(result)
-        req.result = result
-        next()
-    })
-}
-
-module.exports = query
diff --git a/libs/routes/api.js b/libs/routes/api.js
deleted file mode 100644
index aa13166f957aac40cdcbed5e66419e991771b7ce..0000000000000000000000000000000000000000
--- a/libs/routes/api.js
+++ /dev/null
@@ -1,138 +0,0 @@
-var express = require('express')
-var xml = require('js2xmlparser')
-var router = express.Router()
-
-var libs = process.cwd() + '/libs/'
-
-var log = require(libs + 'log')(module)
-var config = require(libs + 'config')
-
-var conn = require(libs + 'db/monet')
-
-router.get('/', function (req, res) {
-    res.json({
-        msg: 'SimCAQ API is running'
-    })
-})
-
-/**
- * Complete range of the enrollments dataset
- *
- * Returns a tuple of start and ending years of the complete enrollments dataset.
- */
-router.get('/year_range', function(req, res) {
-    var yearSql = "SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo) AS end_year FROM turmas AS t";
-    conn.query(yearSql, true).then(function(result) {
-      if (req.query.format === 'csv') {
-        res.csv(result.data);
-      } else if (req.query.format === 'xml') {
-        res.send(xml("result", JSON.stringify({year_range: result.data})))
-      }
-      else {
-        res.json({
-            result: result.data
-        });
-      }
-    });
-})
-
-router.get('/data', function(req, res) {
-    log.debug(req.query)
-    log.debug(req.query.met)
-    log.debug(req.query.dim)
-    conn.query(
-        'SELECT * FROM turmas'
-    ).then(function(result) {
-      if (req.query.format === 'csv') {
-        res.csv(result.data);
-      } else if (req.query.format === 'xml') {
-        res.send(xml("result", JSON.stringify({data: result.data})))
-      }
-      else {
-        res.json({
-            result: result.data
-        });
-      }
-    })
-})
-
-router.get('/enrollments', function(req, res) {
-    var params = req.query;
-    var id = 0;
-    var location_id = 0;
-    var adm_dependency_id = 0;
-    var census_year = 0;
-    var enrollmentSql = "";
-
-    if (params.id)
-    {
-        id = parseInt(params.id, 10);
-    }
-
-    if (params.location_id)
-    {
-        location_id = parseInt(params.location_id, 10);
-    }
-
-    if (params.adm_dependency_id)
-    {
-        adm_dependency_id = parseInt(params.adm_dependency_id, 10);
-    }
-
-    if (params.census_year)
-    {
-        census_year = parseInt(params.census_year, 10);
-    }
-
-    /**
-     * FIXME: parameter substitution in the queries is not safe (vulnerable to
-     * SQL injection). Substitution from MonetDB module is not working for some
-     * reason.
-     */
-    switch(params.aggregate)
-    {
-    case "city":
-        if (id) {
-            enrollmentSql = "SELECT nome AS name, total FROM mat_municipio(" + id + "," + census_year + "," + adm_dependency_id + "," + location_id + ")";
-        } else {
-            enrollmentSql = "SELECT nome AS name, total FROM mat_municipios(" + census_year + "," + adm_dependency_id + "," + location_id + ")";
-        }
-        break;
-    case "state":
-        if (id) {
-            enrollmentSql = "SELECT nome AS name, total FROM mat_estado(" + id + "," + census_year + "," + adm_dependency_id + "," + location_id + ")";
-        } else {
-            enrollmentSql = "SELECT nome AS name, total FROM mat_estados(" + census_year + "," + adm_dependency_id + "," + location_id + ")";
-        }
-        break;
-    case "region":
-        if (id) {
-            enrollmentSql = "SELECT nome AS name, total FROM mat_regiao(" + id + "," + census_year + "," + adm_dependency_id + "," + location_id + ")";
-        } else {
-            enrollmentSql = "SELECT nome AS name, total FROM mat_regioes(" + census_year + "," + adm_dependency_id + "," + location_id + ")";
-        }
-        break;
-    default:
-        enrollmentSql = "SELECT nome AS name, total FROM mat_brasil(" + census_year + "," + adm_dependency_id + "," + location_id + ")";
-    }
-
-    log.debug(params);
-    log.debug("Executing query: " + enrollmentSql);
-
-    conn.query(enrollmentSql, true).then(function(result) {
-        log.debug(result);
-        if (req.query.format === 'csv') {
-          res.csv(result.data);
-        } else if (req.query.format === 'xml') {
-          res.send(xml("result", JSON.stringify({enrollments: result.data})))
-        }
-        else {
-          res.json({
-              result: result.data
-          });
-        }
-        log.debug("All resources were released");
-    });
-})
-
-module.exports = router
diff --git a/libs/routes/cities.js b/libs/routes/cities.js
deleted file mode 100644
index 10d7ceef4cc7ceebad2577ae12fcea5b8d91019a..0000000000000000000000000000000000000000
--- a/libs/routes/cities.js
+++ /dev/null
@@ -1,42 +0,0 @@
-var express = require('express')
-var xml = require('js2xmlparser')
-var router = express.Router()
-var squel = require('squel')
-
-var libs = process.cwd() + '/libs/'
-
-var log = require(libs + 'log')(module)
-var config = require(libs + 'config')
-
-var conn = require(libs + 'db/monet')
-var query = require(libs + 'middlewares/query')
-
-function response(req, res) {
-    if (req.query.format === 'csv') {
-        res.csv(req.result.data)
-    } else if (req.query.format === 'xml') {
-        res.send(xml("result", JSON.stringify({city: req.result.data})))
-    }
-    else {
-        res.json({
-            result: req.result.data
-        })
-    }
-}
-
-router.get('/', function(req, res, next) {
-    req.query = squel.select().from('municipios').toParam()
-    next()
-}, query, response)
-
-router.get('/:id', function(req, res, next) {
-    req.query = squel.select().from('municipios').where('pk_municipio_id=?', parseInt(req.params.id, 10)).toParam()
-    next()
-}, query, response)
-
-router.get('/state/:id', function(req, res, next) {
-    req.query = squel.select().from('municipios').where('fk_estado_id=?', parseInt(req.params.id, 10)).toParam()
-    next()
-}, query, response)
-
-module.exports = router
diff --git a/libs/routes/regions.js b/libs/routes/regions.js
deleted file mode 100644
index cfbbcd946572a8a30ebf00536ce9cbf544226f7f..0000000000000000000000000000000000000000
--- a/libs/routes/regions.js
+++ /dev/null
@@ -1,38 +0,0 @@
-var express = require('express')
-var xml = require('js2xmlparser')
-var router = express.Router()
-var squel = require('squel')
-
-var libs = process.cwd() + '/libs/'
-
-var log = require(libs + 'log')(module)
-var config = require(libs + 'config')
-
-var conn = require(libs + 'db/monet')
-
-var query = require(libs + 'middlewares/query')
-
-function response(req, res) {
-    if (req.query.format === 'csv') {
-        res.csv(req.result.data)
-    } else if (req.query.format === 'xml') {
-        res.send(xml("result", JSON.stringify({state: req.result.data})))
-    }
-    else {
-        res.json({
-            result: req.result.data
-        })
-    }
-}
-
-router.get('/', function(req, res, next) {
-    req.query = squel.select().from('regioes').toParam()
-    next()
-}, query, response)
-
-router.get('/:id', function(req, res, next) {
-    req.query = squel.select().from('regioes').where('pk_regiao_id=?', parseInt(req.params.id, 10)).toParam()
-    next()
-}, query, response)
-
-module.exports = router
diff --git a/libs/routes/states.js b/libs/routes/states.js
deleted file mode 100644
index 79959ef8fd4a19c3afc3f51328ec737396cb0bbe..0000000000000000000000000000000000000000
--- a/libs/routes/states.js
+++ /dev/null
@@ -1,44 +0,0 @@
-var express = require('express')
-var xml = require('js2xmlparser')
-var router = express.Router()
-var squel = require('squel')
-
-var libs = process.cwd() + '/libs/'
-
-var log = require(libs + 'log')(module)
-var config = require(libs + 'config')
-
-var conn = require(libs + 'db/monet')
-
-var query = require(libs + 'middlewares/query')
-
-function response(req, res) {
-    console.log('respostas :)')
-    if (req.query.format === 'csv') {
-        res.csv(req.result.data)
-    } else if (req.query.format === 'xml') {
-        res.send(xml("result", JSON.stringify({state: req.result.data})))
-    }
-    else {
-        res.json({
-            result: req.result.data
-        })
-    }
-}
-
-router.get('/', function(req, res, next) {
-    req.query = squel.select().from('estados').toParam()
-    next()
-}, query, response)
-
-router.get('/:id', function(req, res, next) {
-    req.query = squel.select().from('estados').where('pk_estado_id = ?', parseInt(req.params.id, 10)).toParam()
-    next()
-}, query, response)
-
-router.get('/region/:id', function(req, res, next) {
-    req.query = squel.select().from('estados').where('fk_regiao_id = ?', parseInt(req.params.id, 10)).toParam()
-    next()
-}, query, response)
-
-module.exports = router
diff --git a/package.json b/package.json
index c8c186c70762c34e88b90578f4f20a76b7317209..06ea4fce17d05c284ee4b464d84c5f35352ca443 100644
--- a/package.json
+++ b/package.json
@@ -5,30 +5,52 @@
   "description": "Simulador custo aluno-qualidade",
   "private": true,
   "scripts": {
-    "start": "forever start server.js || node server.js",
-    "test": "mocha"
+    "start": "cd build && forever start server.js || node server.js",
+    "test": "cd build && mocha"
   },
   "dependencies": {
     "apicache": "0.0.14",
     "body-parser": "^1.13.1",
+    "chai": "^3.5.0",
+    "chai-http": "^3.0.0",
     "cookie-parser": "^1.3.5",
     "cors": "^2.7.1",
     "csv-express": "^1.1.0",
     "debug": "~2.0.x",
+    "dirty-chai": "^1.2.2",
     "express": "^4.13.0",
     "faker": "^2.1.5",
     "forever": "^0.15.2",
     "js2xmlparser": "^1.0.0",
     "method-override": "^2.3.3",
-    "monetdb": "^1.1.2",
+    "mocha": "^2.5.3",
+    "monetdb-pool": "0.0.8",
     "nconf": "^0.6.x",
     "squel": "^5.4.2",
     "winston": "^2.2.0"
   },
   "license": "MIT",
   "devDependencies": {
-    "chai": "^3.5.0",
-    "chai-http": "^3.0.0",
-    "mocha": "^2.5.3"
+    "babel-cli": "^6.11.4",
+    "babel-core": "^6.14.0",
+    "babel-preset-es2015": "^6.13.2",
+    "babel-register": "^6.14.0",
+    "babelify": "^7.3.0",
+    "browserify": "^13.1.0",
+    "eslint": "^3.3.1",
+    "eslint-config-airbnb": "^10.0.1",
+    "eslint-plugin-import": "^1.13.0",
+    "eslint-plugin-jsx-a11y": "^2.1.0",
+    "eslint-plugin-react": "^6.1.1",
+    "gulp": "^3.9.1",
+    "gulp-babel": "^6.1.2",
+    "gulp-cli": "^1.2.2",
+    "gulp-eslint": "^3.0.1",
+    "gulp-file-cache": "0.0.1",
+    "gulp-mocha": "^3.0.1",
+    "gulp-nodemon": "^2.1.0",
+    "gulp-plumber": "^1.1.0",
+    "gulp-rename": "^1.2.2",
+    "gulp-uglify": "^2.0.0"
   }
 }
diff --git a/server.js b/server.js
deleted file mode 100644
index f27cd5c80693c1b31bbd500a5fcbae00a86b957d..0000000000000000000000000000000000000000
--- a/server.js
+++ /dev/null
@@ -1,13 +0,0 @@
-var debug = require('debug')('node-express-base')
-
-var libs = process.cwd() + '/libs/'
-var config = require(libs + 'config')
-var log = require(libs + 'log')(module)
-var app = require(libs + 'app')
-
-app.set('port', process.env.PORT || config.get('port') || 3000)
-
-var server = app.listen(app.get('port'), function() {
-  debug('Express server listening on port ' + server.address().port)
-  log.info('Express server listening on port ' + server.address().port)
-})
diff --git a/src/libs/app.js b/src/libs/app.js
new file mode 100644
index 0000000000000000000000000000000000000000..07d7d28fcebce25e8aadc5b9b8e47e09ffd0da96
--- /dev/null
+++ b/src/libs/app.js
@@ -0,0 +1,34 @@
+const express = require('express');
+const cookieParser = require('cookie-parser');
+const bodyParser = require('body-parser');
+const methodOverride = require('method-override');
+const cors = require('cors');
+
+const log = require('./log')(module);
+
+const app = express();
+
+const api = require('./routes/api');
+
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+app.use(cookieParser());
+app.use(cors());
+app.use(methodOverride());
+app.use(api);
+
+// catch 404 and forward to error handler
+app.use((req, res, next) => {
+    res.status(404);
+    log.debug('%s %d %s', req.method, res.statusCode, req.url);
+    res.json({ error: 'Not found' }).end();
+});
+
+// error handlers
+app.use((err, req, res, next) => {
+    res.status(err.status || 500);
+    log.error('%s %d %s', req.method, res.statusCode, err.message);
+    res.json({ error: err.message }).end();
+});
+
+module.exports = app;
diff --git a/src/libs/config.js b/src/libs/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef5a26cddbb3946b89ac690e8cf52d28884463aa
--- /dev/null
+++ b/src/libs/config.js
@@ -0,0 +1,7 @@
+const nconf = require('nconf');
+
+nconf.argv()
+    .env()
+    .file({ file: `${process.cwd()}/config.json` });
+
+module.exports = nconf;
diff --git a/src/libs/db/monet.js b/src/libs/db/monet.js
new file mode 100644
index 0000000000000000000000000000000000000000..1cf874cc9ca5a2ada3e20bdd077c34fbcfbe8226
--- /dev/null
+++ b/src/libs/db/monet.js
@@ -0,0 +1,22 @@
+const MonetDBPool = require('monetdb-pool');
+
+const libs = `${process.cwd()}/libs`;
+
+const config = require(`${libs}/config`);
+
+const poolOptions = {
+    nrConnections: config.get('monetdb:nrConnections'),
+};
+
+const options = {
+    host: config.get('monetdb:host'),
+    port: config.get('monetdb:port'),
+    dbname: config.get('monetdb:dbname'),
+    user: config.get('monetdb:user'),
+    password: config.get('monetdb:password'),
+};
+
+const conn = new MonetDBPool(poolOptions, options);
+conn.connect();
+
+module.exports = conn;
diff --git a/src/libs/db/query_exec.js b/src/libs/db/query_exec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd921d1d02deec90101260046707b7929434a10a
--- /dev/null
+++ b/src/libs/db/query_exec.js
@@ -0,0 +1,44 @@
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const conn = require(`${libs}/db/monet`);
+
+/**
+ * Promise that executes an SQL query with optional parameters
+ *
+ * Examples:
+ *   Query with no parameters:
+ *     execSqlQuery('SELECT * FROM people');
+ *   Query with one parameter:
+ *     execSqlQuery('SELECT name, age FROM people WHERE id = ?', [1]);
+ *   Query with more than one parameter:
+ *     execSqlQuery('SELECT name, age FROM people WHERE city = ? AND age > ?', ['São Paulo', 35]);
+ *
+ * @param {string} sqlQuery       - SQL query to be executed by the Promise
+ * @param {array}  sqlQueryParams - SQL query parameters
+ */
+function execSqlQuery(sqlQuery, sqlQueryParams = []) {
+    log.debug(`Executing SQL query '${sqlQuery}' with params '${sqlQueryParams}'`);
+    return new Promise((resolve, reject) => {
+        conn.prepare(sqlQuery, true).then(
+            (dbQuery) => {
+                dbQuery.exec(sqlQueryParams).then(
+                    (dbResult) => {
+                        log.debug(`Query result: ${dbResult.data}`);
+                        log.debug(dbResult.data);
+                        resolve(dbResult.data);
+                    },
+                    (dbError) => {
+                        log.error(`SQL query execution error: ${dbError.message}`);
+                        reject(new Error(dbError.message));
+                    }
+                );
+                // release resources allocated for prepared statement
+                conn.release();
+            }
+        );
+    });
+}
+
+module.exports = execSqlQuery;
diff --git a/src/libs/log.js b/src/libs/log.js
new file mode 100644
index 0000000000000000000000000000000000000000..ecf6df026314105b02628ef0ac2736a23950374a
--- /dev/null
+++ b/src/libs/log.js
@@ -0,0 +1,42 @@
+const config = require('./config');
+
+const winston = require('winston');
+
+winston.emitErrs = true;
+
+function getFilePath(module) {
+    // using filename in log statements
+    return module.filename.split('/').slice(-2).join('/');
+}
+
+function logger(module) {
+    const log = new winston.Logger({
+        transports: [
+            new winston.transports.File({
+                name: 'info-log',
+                level: 'info',
+                filename: `${process.cwd()}/logs/all.log`,
+                handleException: true,
+                json: false,
+                maxSize: 5242880, // 5MB
+                maxFiles: 2,
+                colorize: false,
+            }),
+            new winston.transports.Console({
+                name: 'debug-log',
+                level: 'debug',
+                label: getFilePath(module),
+                handleException: true,
+                json: false,
+                colorize: true,
+            }),
+        ],
+        exitOnError: false,
+    });
+    if (!config.get('debug')) {
+        log.remove('debug-log');
+    }
+    return log;
+}
+
+module.exports = logger;
diff --git a/src/libs/middlewares/dimensions.js b/src/libs/middlewares/dimensions.js
new file mode 100644
index 0000000000000000000000000000000000000000..8532425cf107ee9f19b13009cdbbdc365e3ebbc3
--- /dev/null
+++ b/src/libs/middlewares/dimensions.js
@@ -0,0 +1,52 @@
+/**
+* Dimensions middleware
+*
+* EXAMPLE:
+* Use it with no parameters to get all the dimensions specified
+* app.get('/', dimensions(), function(req, res, next){})
+*
+* Use it with an array of accepted values
+* app.get('/', dimensions(['year', 'location']), function(req, res, next){})
+*
+* Use it globally
+* app.use(dimensions())
+*/
+
+function intersect(a, b) {
+    let t;
+    if (b.length > a.length) {
+        t = b; b = a; a = t;
+    }
+    return a.filter((e) => b.indexOf(e) !== -1);
+}
+
+function dimensions(dims) {
+    return (req, res, next) => {
+        req.dims = {};
+        if (req.query.dims) {
+            const params = req.query.dims.split(',');
+            const dimObj = {};
+            for (const param of params) {
+                const kv = param.split(':');
+                dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1];
+            }
+            // for(let i=0; i<params.length; ++i) {
+            //     let kv = params[i].split(':');
+            //     dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1];
+            // }
+
+            // If the dims array exists and is not empty
+            if (typeof dims !== 'undefined' && dims.length > 0) {
+                const intersection = intersect(dims, Object.keys(dimObj));
+                for (let i = 0; i < intersection.length; ++i) {
+                    req.dims[intersection[i]] = dimObj[intersection[i]];
+                }
+            } else {
+                req.dims = dimObj;
+            }
+        }
+        next();
+    };
+}
+
+module.exports = dimensions;
diff --git a/src/libs/middlewares/query.js b/src/libs/middlewares/query.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b91fc8f2e86be81f212d4148023f6673c1b6323
--- /dev/null
+++ b/src/libs/middlewares/query.js
@@ -0,0 +1,16 @@
+const libs = `${process.cwd()}/libs`;
+const log = require(`${libs}/log`)(module);
+const execQuery = require(`${libs}/db/query_exec`);
+
+function query(req, res, next) {
+    log.debug(req.query);
+    execQuery(req.query.text, req.query.values).then((result) => {
+        log.debug(result);
+        req.result = result;
+        next();
+    }, (error) => {
+        next(error);
+    });
+}
+
+module.exports = query;
diff --git a/src/libs/middlewares/response.js b/src/libs/middlewares/response.js
new file mode 100644
index 0000000000000000000000000000000000000000..8328b4ffb7947d85280f14ee71be7b3afd8b4a13
--- /dev/null
+++ b/src/libs/middlewares/response.js
@@ -0,0 +1,13 @@
+const xml = require('js2xmlparser');
+
+function response(req, res) {
+    if (req.query.format === 'csv') {
+        res.csv(req.result);
+    } else if (req.query.format === 'xml') {
+        res.send(xml('result', JSON.stringify({ city: req.result })));
+    } else {
+        res.json({ result: req.result });
+    }
+}
+
+module.exports = response;
diff --git a/src/libs/routes/api.js b/src/libs/routes/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a23be8c5b12994d12ce5014c9fee82bbb8a966c
--- /dev/null
+++ b/src/libs/routes/api.js
@@ -0,0 +1,23 @@
+const express = require('express');
+
+const api = express();
+
+const enrollment = require('./enrollment');
+
+const state = require('./state');
+
+const region = require('./region');
+
+const city = require('./city');
+
+api.get('/', (req, res) => {
+    res.json({ msg: 'SimCAQ API is running' });
+});
+
+// mount API routes
+api.use('/v1/enrollment', enrollment);
+api.use('/v1/state', state);
+api.use('/v1/region', region);
+api.use('/v1/city', city);
+
+module.exports = api;
diff --git a/src/libs/routes/city.js b/src/libs/routes/city.js
new file mode 100644
index 0000000000000000000000000000000000000000..aa28900b9f29972aa0d5d8c01c85aba8fd54bd17
--- /dev/null
+++ b/src/libs/routes/city.js
@@ -0,0 +1,36 @@
+const express = require('express');
+
+const cityApp = express();
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`);
+
+const response = require(`${libs}/middlewares/response`);
+
+cityApp.get('/', (req, res, next) => {
+    req.query = squel.select().from('municipios').toParam();
+    next();
+}, query, response);
+
+cityApp.get('/:id', (req, res, next) => {
+    req.query = squel.select().from('municipios').where('pk_municipio_id = ?',
+        parseInt(req.params.id, 10)).toParam();
+    next();
+}, query, response);
+
+cityApp.get('/ibge/:id', (req, res, next) => {
+    req.query = squel.select().from('municipios').where('codigo_ibge = ?',
+        req.params.id).toParam();
+    next();
+}, query, response);
+
+cityApp.get('/state/:id', (req, res, next) => {
+    req.query = squel.select().from('municipios').where('fk_estado_id = ?',
+        parseInt(req.params.id, 10));
+    next();
+}, query, response);
+
+module.exports = cityApp;
diff --git a/src/libs/routes/enrollment.js b/src/libs/routes/enrollment.js
new file mode 100644
index 0000000000000000000000000000000000000000..7ca2be3e020635fed9d51affdaa242b960be93b4
--- /dev/null
+++ b/src/libs/routes/enrollment.js
@@ -0,0 +1,301 @@
+const express = require('express');
+
+const enrollmentApp = express();
+
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const dbQuery = require('../db/query_exec');
+
+const response = require('../middlewares/response');
+
+/**
+ * Complete range of the enrollments dataset
+ *
+ * Returns a tuple of start and ending years of the complete enrollments dataset.
+ */
+enrollmentApp.get('/year_range', (req, res, next) => {
+    const yearSql = 'SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo)'
+        + 'AS end_year FROM turmas AS t';
+
+    dbQuery(yearSql).then((result) => {
+        const record = result['0'];
+        log.debug(record);
+        req.result = { start_year: record.start_year, end_year: record.end_year };
+        return response(req, res);
+    }, (error) => {
+        log.error(`[${req.originalUrl}] SQL query error: ${error}`);
+        next('Internal error, request could not be satisfied at this moment. Please, '
+            + 'try again later');
+    });
+});
+
+enrollmentApp.get('/education_level', (req, res, next) => {
+    const edLevelSql = 'SELECT pk_etapa_ensino_id AS id, desc_etapa AS '
+        + 'education_level FROM etapa_ensino';
+
+    dbQuery(edLevelSql).then((result) => {
+        req.result = result;
+        return response(req, res);
+    }, (error) => {
+        log.error(`[${req.originalUrl}] SQL query error: ${error}`);
+        next('Internal error, request could not be satisfied at this moment. Please, '
+            + 'try again later');
+    });
+});
+
+enrollmentApp.get('/data', (req, res, next) => {
+    const schoolClassSql = 'SELECT * FROM turmas';
+    dbQuery(schoolClassSql).then((result) => {
+        req.result = result;
+        return response(req, res);
+    }, (error) => {
+        log.error(`[${req.originalUrl}] SQL query error: ${error}`);
+        next('Internal error, request could not be satisfied at this moment. Please, '
+            + 'try again later');
+    });
+});
+
+enrollmentApp.use('/', (req, res, next) => {
+    const params = req.query;
+    req.paramCnt = 0;
+
+    if (typeof params.id !== 'undefined') {
+        req.id = parseInt(params.id, 10);
+        req.paramCnt += 1;
+    }
+
+    if (typeof params.location_id !== 'undefined') {
+        req.location_id = parseInt(params.location_id, 10);
+        req.paramCnt += 1;
+    }
+
+    if (typeof params.adm_dependency_id !== 'undefined') {
+        req.adm_dependency_id = parseInt(params.adm_dependency_id, 10);
+        req.paramCnt += 1;
+    }
+
+    if (typeof params.census_year !== 'undefined') {
+        req.census_year = parseInt(params.census_year, 10);
+        req.paramCnt += 1;
+    }
+
+    if (typeof params.education_level_id !== 'undefined') {
+        req.education_level_id = parseInt(params.education_level_id, 10);
+        req.paramCnt += 1;
+    }
+
+    next();
+});
+
+enrollmentApp.use('/', (req, res, next) => {
+    const params = req.query;
+    if (typeof params.aggregate !== 'undefined' && params.aggregate === 'region') {
+        log.debug('Using enrollments query for regions');
+        req.sqlQuery = 'SELECT r.nome AS name, COALESCE(SUM(t.num_matriculas), 0) AS total '
+            + 'FROM regioes AS r '
+            + 'INNER JOIN estados AS e ON r.pk_regiao_id = e.fk_regiao_id '
+            + 'INNER JOIN municipios AS m ON e.pk_estado_id = m.fk_estado_id '
+            + 'LEFT OUTER JOIN turmas AS t ON ( '
+            + 'm.pk_municipio_id = t.fk_municipio_id ';
+        req.sqlQueryParams = [];
+
+        if (typeof req.census_year !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.ano_censo = ?';
+            req.sqlQueryParams.push(req.census_year);
+        }
+
+        if (typeof req.adm_dependency_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.fk_dependencia_adm_id = ?';
+            req.sqlQueryParams.push(req.adm_dependency_id);
+        }
+
+        if (typeof req.location_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.id_localizacao = ?';
+            req.sqlQueryParams.push(req.location_id);
+        }
+
+        if (typeof req.education_level_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.fk_etapa_ensino_id = ?';
+            req.sqlQueryParams.push(req.education_level_id);
+        }
+
+        req.sqlQuery += ')';
+        if (typeof req.id !== 'undefined') {
+            req.sqlQuery += ' WHERE ';
+            req.sqlQuery += 'r.pk_regiao_id = ?';
+            req.sqlQueryParams.push(req.id);
+        }
+        req.sqlQuery += ' GROUP BY r.nome';
+    }
+    next();
+});
+
+enrollmentApp.use('/', (req, res, next) => {
+    const params = req.query;
+    if (typeof params.aggregate !== 'undefined' && params.aggregate === 'state') {
+        log.debug('Using enrollments query for states');
+        req.sqlQuery = 'SELECT e.nome AS name, COALESCE(SUM(t.num_matriculas), 0) as total '
+            + 'FROM estados AS e '
+            + 'INNER JOIN municipios AS m ON m.fk_estado_id = e.pk_estado_id '
+            + 'LEFT OUTER JOIN turmas AS t ON ('
+            + 'm.pk_municipio_id = t.fk_municipio_id ';
+        req.sqlQueryParams = [];
+
+        if (typeof req.census_year !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.ano_censo = ?';
+            req.sqlQueryParams.push(req.census_year);
+        }
+
+        if (typeof req.adm_dependency_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.fk_dependencia_adm_id = ?';
+            req.sqlQueryParams.push(req.adm_dependency_id);
+        }
+
+        if (typeof req.location_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.id_localizacao = ?';
+            req.sqlQueryParams.push(req.location_id);
+        }
+
+        if (typeof req.education_level_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.fk_etapa_ensino_id = ?';
+            req.sqlQueryParams.push(req.education_level_id);
+        }
+
+        req.sqlQuery += ')';
+
+        if (typeof req.id !== 'undefined') {
+            req.sqlQuery += ' WHERE ';
+            req.sqlQuery += 'e.pk_estado_id = ?';
+            req.sqlQueryParams.push(req.id);
+        }
+
+        req.sqlQuery += ' GROUP BY e.nome';
+    }
+    next();
+});
+
+enrollmentApp.use('/', (req, res, next) => {
+    const params = req.query;
+    if (typeof params.aggregate !== 'undefined' && params.aggregate === 'city') {
+        log.debug('Using enrollments query for cities');
+        req.sqlQuery = 'SELECT m.nome AS name, COALESCE(SUM(t.num_matriculas), 0) as total '
+            + 'FROM municipios AS m '
+            + 'LEFT OUTER JOIN turmas AS t ON ( '
+            + 'm.pk_municipio_id = t.fk_municipio_id';
+        req.sqlQueryParams = [];
+
+        if (typeof req.census_year !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.ano_censo = ?';
+            req.sqlQueryParams.push(req.census_year);
+        }
+
+        if (typeof req.adm_dependency_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.fk_dependencia_adm_id = ?';
+            req.sqlQueryParams.push(req.adm_dependency_id);
+        }
+
+        if (typeof req.location_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.id_localizacao = ?';
+            req.sqlQueryParams.push(req.location_id);
+        }
+
+        if (typeof req.education_level_id !== 'undefined') {
+            req.sqlQuery += ' AND ';
+            req.sqlQuery += 't.fk_etapa_ensino_id = ?';
+            req.sqlQueryParams.push(req.education_level_id);
+        }
+
+        req.sqlQuery += ')';
+
+        if (typeof req.id !== 'undefined') {
+            req.sqlQuery += ' WHERE ';
+            req.sqlQuery += 'm.pk_municipio_id = ?';
+            req.sqlQueryParams.push(req.id);
+        }
+
+        req.sqlQuery += 'GROUP BY m.nome';
+    }
+    next();
+});
+
+enrollmentApp.use('/', (req, res, next) => {
+    const params = req.query;
+    if (typeof params.aggregate === 'undefined') {
+        log.debug('Using enrollments query for the whole country');
+        req.sqlQuery = 'SELECT \'Brasil\' AS name, COALESCE(SUM(t.num_matriculas),0) AS total '
+            + 'FROM turmas AS t';
+        req.sqlQueryParams = [];
+
+        if (req.paramCnt > 0) {
+            req.sqlQuery += ' WHERE ';
+        }
+
+        if (typeof req.census_year !== 'undefined') {
+            req.sqlQuery += 't.ano_censo = ?';
+            req.sqlQueryParams.push(req.census_year);
+        }
+
+        if (typeof req.adm_dependency_id !== 'undefined') {
+            if (req.sqlQueryParams.length > 0) {
+                req.sqlQuery += ' AND ';
+            }
+            req.sqlQuery += 't.fk_dependencia_adm_id = ?';
+            req.sqlQueryParams.push(req.adm_dependency_id);
+        }
+
+        if (typeof req.location_id !== 'undefined') {
+            if (req.sqlQueryParams.length > 0) {
+                req.sqlQuery += ' AND ';
+            }
+            req.sqlQuery += 't.id_localizacao = ?';
+            req.sqlQueryParams.push(req.location_id);
+        }
+
+        if (typeof req.education_level_id !== 'undefined') {
+            if (req.sqlQueryParams.length > 0) {
+                req.sqlQuery += ' AND ';
+            }
+            req.sqlQuery += 't.fk_etapa_ensino_id = ?';
+            req.sqlQueryParams.push(req.education_level_id);
+        }
+    }
+    next();
+});
+
+enrollmentApp.get('/', (req, res, next) => {
+    log.debug(`Request parameters: ${req}`);
+    if (typeof req.sqlQuery === 'undefined') {
+        // Should only happen if there is a bug in the chaining of the
+        // '/enrollments' route, since when no +aggregate+ parameter is given,
+        // it defaults to use the query for the whole country.
+        log.error('BUG -- No SQL query was found to be executed!');
+        next('Internal error, request could not be satisfied at this moment. Please, '
+            + 'try again later');
+    } else {
+        log.debug('SQL query: ${ req.sqlQuery }?');
+        log.debug(req.sqlQuery);
+        dbQuery(req.sqlQuery).then((result) => {
+            req.result = result;
+            return response(req, res);
+        }, (error) => {
+            log.error(`[${req.originalUrl}] SQL query error: ${error}`);
+            next('Internal error, request could not be satisfied at this moment. Please, '
+                + 'try again later');
+        });
+    }
+});
+
+module.exports = enrollmentApp;
diff --git a/src/libs/routes/region.js b/src/libs/routes/region.js
new file mode 100644
index 0000000000000000000000000000000000000000..56c4cd2e44640ccd8a166e49afae97b567bb51ec
--- /dev/null
+++ b/src/libs/routes/region.js
@@ -0,0 +1,24 @@
+const express = require('express');
+
+const regionApp = express();
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`);
+
+const response = require(`${libs}/middlewares/response`);
+
+regionApp.get('/', (req, res, next) => {
+    req.query = squel.select().from('regioes').toParam();
+    next();
+}, query, response);
+
+regionApp.get('/:id', (req, res, next) => {
+    req.query = squel.select().from('regioes').where('pk_regiao_id = ?',
+        parseInt(req.params.id, 10)).toParam();
+    next();
+}, query, response);
+
+module.exports = regionApp;
diff --git a/src/libs/routes/state.js b/src/libs/routes/state.js
new file mode 100644
index 0000000000000000000000000000000000000000..dae0eb713ee44d43b4f9cca82e06ee32fa3b1582
--- /dev/null
+++ b/src/libs/routes/state.js
@@ -0,0 +1,30 @@
+const express = require('express');
+
+const stateApp = express();
+
+const libs = `${process.cwd()}/libs`;
+
+const squel = require('squel');
+
+const query = require(`${libs}/middlewares/query`);
+
+const response = require(`${libs}/middlewares/response`);
+
+stateApp.get('/', (req, res, next) => {
+    req.query = squel.select().from('estados').toParam();
+    next();
+}, query, response);
+
+stateApp.get('/:id', (req, res, next) => {
+    req.query = squel.select().from('estados').where('pk_estado_id = ?',
+        parseInt(req.params.id, 10)).toParam();
+    next();
+}, query, response);
+
+stateApp.get('/region/:id', (req, res, next) => {
+    req.query = squel.select().from('estados').where('fk_regiao_id = ?',
+        parseInt(req.params.id, 10)).toParam();
+    next();
+}, query, response);
+
+module.exports = stateApp;
diff --git a/src/server.js b/src/server.js
new file mode 100644
index 0000000000000000000000000000000000000000..7b3927e3fc64fe9f0001f01e95b0a2805feb33eb
--- /dev/null
+++ b/src/server.js
@@ -0,0 +1,16 @@
+const debug = require('debug')('node-express-base');
+
+const libs = `${process.cwd()}/libs`;
+
+const config = require(`${libs}/config`);
+
+const log = require(`${libs}/log`)(module);
+
+const app = require(`${libs}/app`);
+
+app.set('port', config.get('port') || 3000);
+
+const server = app.listen(app.get('port'), () => {
+    debug(`Express server listening on port ${server.address().port}`);
+    log.info(`Express server listening on port ${config.get('port')}`);
+});
diff --git a/src/test/test.js b/src/test/test.js
new file mode 100644
index 0000000000000000000000000000000000000000..dde277d0dec15186290a5a0aa992dd60d8e5c7a4
--- /dev/null
+++ b/src/test/test.js
@@ -0,0 +1,162 @@
+const chai = require('chai');
+
+const dirtyChai = require('dirty-chai');
+
+chai.use(dirtyChai);
+
+const chaiHttp = require('chai-http');
+
+const assert = chai.assert;
+
+const expect = chai.expect;
+
+const should = chai.should(); // actually call the function
+
+const server = require('../libs/app');
+
+chai.use(chaiHttp);
+
+describe('request enrollments', () => {
+    it('should list enrollments', (done) => {
+        chai.request(server)
+            .get('/v1/enrollment')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('name');
+                res.body.result[0].should.have.property('total');
+                done();
+            });
+    });
+});
+
+describe('request regions', () => {
+    it('should list all regions', (done) => {
+        chai.request(server)
+            .get('/v1/region')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('pk_regiao_id');
+                res.body.result[0].should.have.property('nome');
+                done();
+            });
+    });
+
+    it('should list region by id', (done) => {
+        chai.request(server)
+            .get('/v1/region/1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result.should.have.length(1);
+                res.body.result[0].should.have.property('pk_regiao_id');
+                res.body.result[0].should.have.property('nome');
+                done();
+            });
+    });
+});
+
+describe('request states', () => {
+    it('should list all states', (done) => {
+        chai.request(server)
+            .get('/v1/state')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('pk_estado_id');
+                res.body.result[0].should.have.property('fk_regiao_id');
+                res.body.result[0].should.have.property('nome');
+                done();
+            });
+    });
+
+    it('should list a state by id', (done) => {
+        chai.request(server)
+            .get('/v1/state/11')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result.should.have.length(1);
+                res.body.result[0].should.have.property('pk_estado_id');
+                res.body.result[0].should.have.property('fk_regiao_id');
+                res.body.result[0].should.have.property('nome');
+                done();
+            });
+    });
+
+    it('should list states by region id', (done) => {
+        chai.request(server)
+            .get('/v1/state/region/1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('pk_estado_id');
+                res.body.result[0].should.have.property('fk_regiao_id');
+                res.body.result[0].should.have.property('nome');
+                done();
+            });
+    });
+});
+
+describe('request cities', () => {
+    it('should list all cities', (done) => {
+        chai.request(server)
+            .get('/v1/city')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('pk_municipio_id');
+                res.body.result[0].should.have.property('fk_estado_id');
+                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('codigo_ibge');
+                done();
+            });
+    });
+
+    it('should list a city by id', (done) => {
+        chai.request(server)
+            .get('/v1/city/1')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('pk_municipio_id');
+                res.body.result[0].should.have.property('fk_estado_id');
+                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('codigo_ibge');
+                done();
+            });
+    });
+
+    it('should list a city by codigo_ibge', (done) => {
+        chai.request(server)
+            .get('/v1/city/ibge/1200013')
+            .end((err, res) => {
+                res.should.have.status(200);
+                res.should.be.json;
+                res.body.should.have.property('result');
+                res.body.result.should.be.a('array');
+                res.body.result[0].should.have.property('pk_municipio_id');
+                res.body.result[0].should.have.property('fk_estado_id');
+                res.body.result[0].should.have.property('nome');
+                res.body.result[0].should.have.property('codigo_ibge');
+                done();
+            });
+    });
+});
diff --git a/test/test.js b/test/test.js
deleted file mode 100644
index d6b67ff84daf91c4abcb98e014cd8beee5c5c42f..0000000000000000000000000000000000000000
--- a/test/test.js
+++ /dev/null
@@ -1,142 +0,0 @@
-var chai = require('chai');
-var chaiHttp = require('chai-http');
-var assert = chai.assert;
-var expect = chai.expect;
-var should = chai.should(); //actually call the function
-var server = require('../libs/app');
-
-chai.use(chaiHttp);
-
-describe('request enrollments', function(){
-
-  it('should list enrollments', function(done){
-    chai.request(server)
-      .get('/api/v1/enrollments')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result[0].should.have.property('name');
-        res.body.result[0].should.have.property('total');
-        done();
-      })
-  });
-});
-
-describe('request regions', function(){
-
-  it('should list all regions', function(done){
-    chai.request(server)
-      .get('/api/v1/regions')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result[0].should.have.property('pk_regiao_id');
-        res.body.result[0].should.have.property('nome');
-        done();
-      })
-  });
-
-  it('should list region by id', function(done){
-    chai.request(server)
-      .get('/api/v1/regions/1')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result.should.have.length(1);
-        res.body.result[0].should.have.property('pk_regiao_id');
-        res.body.result[0].should.have.property('nome');
-        done();
-      })
-  });
-});
-
-describe('request states', function(){
-
-  it('should list all states', function(done){
-    chai.request(server)
-      .get('/api/v1/states')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result[0].should.have.property('pk_estado_id');
-        res.body.result[0].should.have.property('fk_regiao_id');
-        res.body.result[0].should.have.property('nome');
-        done();
-      })
-  });
-
-  it('should list a state by id', function(done){
-    chai.request(server)
-      .get('/api/v1/states/11')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result.should.have.length(1);
-        res.body.result[0].should.have.property('pk_estado_id');
-        res.body.result[0].should.have.property('fk_regiao_id');
-        res.body.result[0].should.have.property('nome');
-        done();
-      })
-  });
-
-  it('should list states by region id', function(done){
-    chai.request(server)
-      .get('/api/v1/states/region/1')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result[0].should.have.property('pk_estado_id');
-        res.body.result[0].should.have.property('fk_regiao_id');
-        res.body.result[0].should.have.property('nome');
-        done();
-      })
-  });
-});
-
-describe('request cities', function(){
-
-  it('should list all cities', function(done){
-    chai.request(server)
-      .get('/api/v1/cities')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result[0].should.have.property('pk_municipio_id');
-        res.body.result[0].should.have.property('fk_estado_id');
-        res.body.result[0].should.have.property('nome');
-        res.body.result[0].should.have.property('codigo_ibge');
-        done();
-      })
-  });
-
-  it('should list a city by id', function(done){
-    chai.request(server)
-      .get('/api/v1/cities/1')
-      .end(function(err, res){
-        res.should.have.status(200);
-        res.should.be.json;
-        res.body.should.have.property('result');
-        res.body.result.should.be.a('array');
-        res.body.result[0].should.have.property('pk_municipio_id');
-        res.body.result[0].should.have.property('fk_estado_id');
-        res.body.result[0].should.have.property('nome');
-        res.body.result[0].should.have.property('codigo_ibge');
-        done();
-      })
-  });
-
-});