3

I am creating a node and express REST application with a PostgreSQL database.

My question is how to define the connection variable globally in a minimalist express application (for a Hello World example)?

I have the following file structure with the following key files included.

  • {PROJECT_ROOT}\bin\www
  • {PROJECT_ROOT}\app.js
  • {PROJECT_ROOT}\routes\index.js
  • {PROJECT_ROOT}\db\db.js
  • {PROJECT_ROOT}\db\location.js
  • {PROJECT_ROOT}\OTHER FILES

The db.js should contain definition of a variable for a connection to the PostgreSQL database globally. This variable should be shared by other modules whenever necessay so that duplicated connections should be avoided.

db.js

var promise = require('bluebird');

/**
 *Use dotenv to read .env vars into Node
 */
require('dotenv').config();

const  options = {
  // Initialization Options
  promiseLib: promise,
  connect(client, dc, useCount) {
    const cp = client.connectionParameters;
    console.log('Connected to database:', cp.database);
  }
};

const  pgp = require('pg-promise')(options);
const  connectionString = process.env.PG_CONN_STR;
const  db = pgp(connectionString);

module.exports = {
    pgp, db
};

location.js defines the business logic to manipulate the gcur_point_location table.

var db_global = require('./db');
var db = db_global.db;

// add query functions

module.exports = {
  getAllLocations: getAllLocations,
  getLocation: getLocation,
  createLocation: createLocation,
  updateLocation: updateLocation,
  removeLocation: removeLocation
};

function getAllLocations(req, res, next) {
  db.any('select * from gcur_point_location')
    .then(function (data) {
      res.status(200)
        .json({
          status: 'success',
          data: data,
          message: 'Retrieved ALL GCUR Point Locations'
        });
    })
    .catch(function (err) {
      return next(err);
    });
}

function getLocation(req, res, next) {
  var locationId = parseInt(req.params.id);
  db.one('select * from gcur_point_location where locationid = $1', locationId)
    .then(function (data) {
      res.status(200)
        .json({
          status: 'success',
          data: data,
          message: 'Retrieved ONE Location by Id'
        });
    })
    .catch(function (err) {
      return next(err);
    });
}

function createLocation(req, res, next) {
  req.body.age = parseInt(req.body.age);
  db.none('insert into gcur_point_location(locationname, locationstatus, lng, lat)' +
      'values(${locationname}, ${locationstatus}, ${lng}, ${lat})',
    req.body)
    .then(function () {
      res.status(200)
        .json({
          status: 'success',
          message: 'Inserted one Location'
        });
    })
    .catch(function (err) {
      return next(err);
    });
}

function updateLocation(req, res, next) {
  db.none('update gcur_point_location set locationname=$1, locationstatus=$2, lng=$3, lat=$4 where locationid=$5',
    [req.body.locationname, req.body.locationstatus, parseFloat(req.body.lng),
      parseFloat(req.body.lat), parseInt(req.params.id)])
    .then(function () {
      res.status(200)
        .json({
          status: 'success',
          message: 'Updated Location'
        });
    })
    .catch(function (err) {
      return next(err);
    });
}

function removeLocation(req, res, next) {
  var locationId = parseInt(req.params.id);
  db.result('delete from gcur_point_location where locationid=$1', locationId)
    .then(function (result) {
      /* jshint ignore:start */
      res.status(200)
        .json({
          status: 'success',
          message: `Removed ${result.rowCount} Location`
        });
      /* jshint ignore:end */
    })
    .catch(function (err) {
      return next(err);
    });
}

Likewise, munipulation of different tables will be defined in individual js files. All of them will require the db.js.

routes/index.js

var express = require('express');
var router = express.Router();

var db = require('../db/location');

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

router.get('/api/locations', db.getAllLocations);
router.get('/api/location/:id', db.getLocation);
router.post('/api/location', db.createLocation);
router.put('/api/location/:id', db.updateLocation);
router.delete('/api/location/:id', db.removeLocation);

module.exports = router;

app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

I would like to have some ideas about whether the above code is a good or a bad practise or any potential failure?

alextc
  • 3,206
  • 10
  • 63
  • 107

2 Answers2

1

Best practices for structuring a database layer with pg-promise are shown in pg-promise-demo.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
0

Most of the code makes sense to me, though I would implement your own ORM and model layers, so you can remove some of the code for PSQL queries and follow MVC design pattern. If all you are building is an express api server, then you do not need the View portion.

I usually have a file called ORM, which has something similar to the following:

var orm = {
  all: function(tableInput, cb) {
    var queryString = "SELECT * FROM " + tableInput + ";";
    connection.query(queryString, function(err, result) {
      if (err) {
        throw err;
      }
      cb(result);
    });
  },
  create: function(table, cols, vals, cb) {
    var queryString = "INSERT INTO " + table;

    queryString += " (";
    queryString += cols.toString();
    queryString += ") ";
    queryString += "VALUES (";
    queryString += printQuestionMarks(vals.length);
    queryString += ") ";

    console.log(queryString);

    connection.query(queryString, vals, function(err, result) {
      if (err) {
        throw err;
      }

      cb(result);
    });
  },
  // An example of objColVals would be {name: panther, sleepy: true}
  update: function(table, objColVals, condition, cb) {
    var queryString = "UPDATE " + table;

    queryString += " SET ";
    queryString += objToSql(objColVals);
    queryString += " WHERE ";
    queryString += condition;

    console.log(queryString);
    connection.query(queryString, function(err, result) {
      if (err) {
        throw err;
      }

      cb(result);
    });
  }
};

// Export the orm object for the model (cat.js).
module.exports = orm;

Then I define a model file for each table you have in psql as following:

// Import the ORM to create functions that will interact with the database.
var orm = require("../config/orm.js");

var cat = {
  all: function(cb) {
    orm.all("cats", function(res) {
      cb(res);
    });
  },
  // The variables cols and vals are arrays.
  create: function(cols, vals, cb) {
    orm.create("cats", cols, vals, function(res) {
      cb(res);
    });
  },
  update: function(objColVals, condition, cb) {
    orm.update("cats", objColVals, condition, function(res) {
      cb(res);
    });
  }
};

// Export the database functions for the controller (catsController.js).
module.exports = cat;

A controller:

var express = require("express");

var router = express.Router();

// Import the model (cat.js) to use its database functions.
var cat = require("../models/cat.js");

// Create all our routes and set up logic within those routes where required.
router.get("/", function(req, res) {
  cat.all(function(data) {
    var hbsObject = {
      cats: data
    };
    console.log(hbsObject);
    res.render("index", hbsObject);
  });
});

router.post("/api/cats", function(req, res) {
  cat.create(["name", "sleepy"], [req.body.name, req.body.sleepy], function(result) {
    // Send back the ID of the new quote
    res.json({ id: result.insertId });
  });
});

router.put("/api/cats/:id", function(req, res) {
  var condition = "id = " + req.params.id;

  console.log("condition", condition);

  cat.update(
    {
      sleepy: req.body.sleepy
    },
    condition,
    function(result) {
      if (result.changedRows === 0) {
        // If no rows were changed, then the ID must not exist, so 404
        return res.status(404).end();
      }
      res.status(200).end();

    }
  );
});

// Export routes for server.js to use.
module.exports = router;

This follows MVC design pattern which is very easy to read and understand. So my whole folder structure would look something like this:

sample image

leogoesger
  • 3,476
  • 5
  • 33
  • 71