Basic RESTful Service – Part II

It is a rainy and stormy day in the Twin Cities of Minneapolis and St. Paul. All is well with rain, but I do shutdown my computers as soon as we get lightning. Have my computers connected via different power supplies, but on more than one occasion I have lost multiple components. So as soon as I hear thunder, I immediately shutdown and read until the storm subsides.

I worked in 2-hour blocks. At the end of the second block I went up and prepare tea. My wife and I have this morning ritual. What was unique today is that a rafter of wild turkeys was in our backyard. Last Friday the tom had two females and they were stopping traffic on the parkway. Today the rafter has grown to include seven females. There are several large parks and dozens of lakes and ponds in this area. We see turkeys year round.

In this post we will continue experimenting with features in Node.js in order to clarify how would we implement a simple RESTful server to store DICOM data. We will continue from where we left in the previous post in this sequence.

This post is based on a course that I am currently subscribed to by Mosh Coding Made Simple. I practice the technique by Richard Feynman of reading, experimenting and explaining what you just covered.

Middleware in Express refers to elements in the processing pipeline that start when the server receives a request and ends when it sends the response back to the client. You can use existing or create your own middleware functions in Express.

Middleware is typically used for logging, authentication, and authorization among other tasks. Middleware functions are called in sequence. It is good practice to write each middleware function in separate modules / files.

An addition to the index.js file to support urlencoded POST requests follows:

// **** parses incoming requests with urlencoded payloads (e.g., key=value&key=value) (populates req.body)
//      traditional approach; not use often ****
app.use(express.urlencoded({ extended: true }));

The following contains notes while the code was being tested:

Postman:

http://localhost:4444/api/patients/?name="Donald Duck"

GET:
[
    {
        "id": 1,
        "name": "James Bond"
    },
    {
        "id": 2,
        "name": "Money Penny"
    },
    {
        "id": 3,
        "name": "John Canessa"
    },
    {
        "id": 4,
        "name": "Jane Doe"
    },
    {
        "id": 5,
        "name": "John Doe"
    }
]

POST:
{
    "id": 6,
    "name": " Donald Duck"
}

GET:
[
    {
        "id": 1,
        "name": "James Bond"
    },
    {
        "id": 2,
        "name": "Money Penny"
    },
    {
        "id": 3,
        "name": "John Canessa"
    },
    {
        "id": 4,
        "name": "Jane Doe"
    },
    {
        "id": 5,
        "name": "John Doe"
    },
    {
        "id": 6,
        "name": " Donald Duck"
    }
]

The server was stopped and started. I used Postman to send a GET request.  That listed the base number of patients. Then a POST request specifying a new patient name as part of the URL was sent which returned the new patient with its patient ID. Finally a GET request for all patients was sent.

We will create two middleware functions. They will be created in their respective files. The first one will be used to log operations to the console and the second will simulate authenticating the user. In practice there are modules that can assist you to log data. Always check the npm libraries before developing new code, but in case the specific behavior you seek is not available, then implement your own code.

The code to log follows:

/*
 * create custom logging (middleware function)
 */
function log(req, res, next) {
  // **** simulate some type of logging operation ****
  console.log("logging...");

  // **** pass control to the next middleware function ****
  next();
}

// **** export log function ****
module.exports = log;

The code for authentication follows:

/*
 * create custom authentication (middleware function)
 */
function authenticate(req, res, next) {
  // **** simulate some type of authentication operation ****
  console.log("authenticating...");

  // **** pass control to the next middleware function ****
  next();
}

// **** export authenticate function ****
module.exports = authenticate;

The code in the index.js main function follows:

// **** for our custom logging ****
const logger = require("./logger.js");

// **** for our custom authentication ****
const authenticate = require("./authenticate");

// **** enable our logger middleware ****
app.use(logger);

// **** enable our authentication middleware ****
app.use(authenticate);

When we issue a request, we should able to see the log and the authenticate functions being called. This is illustrated in the following screen capture:

C:\Users\John\express-demo>nodemon
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
  app:startup Morgan enabled... +0ms
storage server listening on port:  4444 ...
logging...          <====
authenticating...   <====

We will now install and use helmet. Helmet helps secure your apps by setting various HTTP headers. You can read more about it in the documentation pointed to by the link in this paragraph.

Another quite useful middleware module is Morgan. It is used to log information to a console or a file. We first need to install it:

C:\Users\John\express-demo>npm install morgan
npm WARN express-demo@1.0.0 No description
npm WARN express-demo@1.0.0 No repository field.

+ morgan@1.9.1
added 3 packages from 2 contributors and audited 157 packages in 2.314s
found 0 vulnerabilities

There are various configured formats. If none suits your needs you can create your own. A list of the pre-defined formats follows:

combined

Standard Apache combined log output.

::1 – – [21/Apr/2019:14:08:08 +0000] “GET /api/patients/ HTTP/1.1” 200 145 “-” “PostmanRuntime/7.6.1”

common

Standard Apache common log output.

::1 – – [21/Apr/2019:14:07:19 +0000] “GET /api/patients/ HTTP/1.1” 200 145

dev

Concise output colored by response status for development use.

The :status token will be colored red for server error codes,

yellow for client error codes,

cyan for redirection codes,

and uncolored for all other codes.

GET /api/patients/ 200 11.159 ms – 145

short

Shorter than default, also including response time.

::1 – GET /api/patients/ HTTP/1.1 200 145 – 11.690 ms

tiny

The minimal output.

GET /api/patients/ 200 145 – 9.251 ms

In the index.js file we need to insert the following set of line:

// **** for morgan logger [a] ****
var fs = require("fs");

// **** morgan logger [b] ****
const morgan = require("morgan");

// **** for morgan to log to a file  ****
const path = require("path");

// **** enable logging if needed ****
if (app.get("env") === "development") {
  // **** create a write stream (in append mode) [d] ****
  const serverLogStream = fs.createWriteStream(
    path.join("./logs", "server.log"),
    {
      flags: "a"
    }
  );

  // **** setup the logger using a predefined format: tiny, short, dev, common, combined [e] ****
  app.use(morgan("common", { stream: serverLogStream }));

  // **** inform the user what is going on ****
  // console.log("Morgan enabled...");
  debug("Morgan enabled...");
}

Note that we are going to write the log to a file. For development and more important, for maintenance in the field, it is very important to keep a short but useful set of log files. I wish I had a penny every time a customer complained of something caused by a set of actions, to find out that the actual set of operations was quite different than what our development team was told.

The following screen capture illustrates that three calls were made to the storage server. We then type the contents of the server.log file.

storage server listening on port:  4444 ...
logging ...
authenticating ...
logging ...
authenticating ...
logging ...
authenticating ...

C:\Users\John\express-demo>type logs\server.log
::1 - - [21/Apr/2019:14:37:39 +0000] "GET /api/patients/ HTTP/1.1" 200 145
::1 - - [21/Apr/2019:14:37:40 +0000] "GET /api/patients/ HTTP/1.1" 200 145
::1 - - [21/Apr/2019:14:37:42 +0000] "GET /api/patients/ HTTP/1.1" 200 145

As you can see the information in the log file has more depth and is written permanently to a file.

About two decades ago I was the architect, designer and principal lead for the development of a storage server. For performance reasons the server was written in C and C++. One of the dozens of libraries was used to log messages to files. An example of a line from one log follows:

12/17/18 13:27:02 0x00001de8 - PropertiesThread <<< status: -1029 propagateQuery: 0 (bool) guid ==>0011114b864e205296245c17f85b82f5<== line: 718 file ==>..\..\..\sdm\source\propertiesthread.c<==

The entry contains date and time. It is followed by the thread ID which wrote the entry. The function / method that encountered an issue is PropertiesThread(). The function returned status -1029. We decided for reasons beyond this post on using numeric codes which map to literals (e.g., <<< WAR_RECORD_NOT_FOUND (-1029) Record NOT found). There are over 1,000 unique error codes. When an error occurs, the values of a set of variables may be displayed. This provides the person reading the log context. Finally the line number and file name are displayed. This helps developers quickly locate the source code where the issue was encountered.

Each source code file is associated with a trace that can be set via an environment variable or a registry key. Windows provides a nice interface to view and edit registry keys (regkey.exe).

The login mechanism generates files with a maxim size (e.g., 2048 lines). When the line count limit is exceeded, a new file named using the date and version (e.g., L20190422_5.txt) is created and all log operations are directed to the now current file.

One can set the limit as how many log files should be created per day. When the limit is reached, log entries are discarded. The reason is that depending on the number of messages (i.e., thousands of messages per minute), the file system could be easily filled in a short periods of times (e.g., a long weekend).

One more feature is that when you set a trace registry key or system environment variable, you can specify a number [0 : n]. Zero (0) represents nothing to log except errors. As the value increases, more and more messages are written to the log file. When in production, by default, all traces are set to 0. The person looking for the cause of an issue can enable trace messages with a necessary level in as many files as needed. Do not underestimate the value of log files. Any system that I develop must include a flexible log facility.

In real life there are different environments use to develop, test and deliver software. Let’s see how we can address this with Node.js.

To set an environment variable, we can use the NODE_ENV environment variable and set it to something (e.g., production). The following code illustrates how this could be done:

// **** display our environment (different ways) ****
console.log(`      NODE_ENV: ${process.env.NODE_ENV}`);
console.log(`app.get('env'): ${app.get("env")}`);

// **** enable logging if needed ****
if (app.get("env") === "development") {
  // **** create a write stream (in append mode) [d] ****
  const serverLogStream = fs.createWriteStream(
    path.join("./logs", "server.log"),
    {
      flags: "a"
    }
  );

  // **** setup the logger using a predefined format: tiny, short, dev, common, combined [e] ****
  app.use(morgan("common", { stream: serverLogStream }));

  // **** inform the user what is going on ****
  // console.log("Morgan enabled...");
  debug("Morgan enabled...");
}

In the following screen capture, we see how morgan was enabled (development) and on the second pass, after changing the value of the system environment value (production), morgan was not enabled:

C:\Users\John\express-demo>nodemon
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
NODE_ENV: undefined         <====
app.get('env'): development <==== Morgan enabled !!! storage server listening on port: 4444 ... C:\Users\John\express-demo>nodemon
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
NODE_ENV: production        <====
app.get('env'): production  <====
storage server listening on port:  4444 ...

Now let’s install the config npm package. You can learn more about it here (https://www.npmjs.com/package/config).

C:\Users\John\express-demo>npm install config
npm WARN express-demo@1.0.0 No description
npm WARN express-demo@1.0.0 No repository field.

+ config@3.1.0
added 3 packages from 6 contributors and audited 160 packages in 4.048s
found 0 vulnerabilities

C:\Users\John\express-demo>type package.json
{
  "name": "express-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "config": "^3.1.0",     <====
    "express": "^4.16.4",
    "helmet": "^3.16.0",
    "joi": "^13.1.0",
    "morgan": "^1.9.1"
  }
}

We need to create a set of files to define different configurations for our storage server. The files can be created using VS Code. The files are created in the config folder which can also be created using VS Code. The following screen capture shows the contents of the config folder:

C:\Users\John\express-demo>type config\*.json

config\custom-environment-variables.json    <====


{
  "mail": {
    "password": "server_password"
  }
}

config\default.json


{
  "name": "Storage Server - Default"
}

config\development.json                     <====


{
  "name": "Storage Server - Development",
  "mail": {
    "host": "CONDOR",
    "port": 1111
  }
}

config\production.json


{
  "name": "Storage Server - Production",
  "mail": {
    "host": "EAGLE",
    "port": 2222
  }
}

We can now set the environment and start the storage server using nodemon:

C:\Users\John\express-demo>set NODE_ENV=development

C:\Users\John\express-demo>echo %NODE_ENV%
development

C:\Users\John\express-demo>nodemon index.js
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
       Mail Port: 1111
storage server listening on port:  4444 ...

You can see that the server selected the development environment which we previously defined.

In general it is not a good idea to store secrets in a file. Doing so will sooner or later compromise the software. If we need to store a password for the storage server we can so by defining a system environment variable which can be read by our server at runtime. The following screen capture illustrates how to set one and access it at runtime:

C:\Users\John\express-demo>set server_password=1234     <==== C:\Users\John\express-demo>echo %server_password%
1234

C:\Users\John\express-demo>nodemon index.js
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
       Mail Port: 1111
   Mail Password: 1234                                  <====
storage server listening on port:  4444 ...

We have all have used print statements to debug code. The issue is that one has to remove them when done. Later, you or someone else might need to repeat the process in order to debug or enhance the code. A better approach to using console.log() is to use the debug module. The following screen capture illustrates how to install the debug module:

C:\Users\John\express-demo>npm install debug
npm WARN express-demo@1.0.0 No description
npm WARN express-demo@1.0.0 No repository field.

+ debug@4.1.1
added 6 packages from 3 contributors, updated 1 package and audited 162 packages in 3.008s
found 0 vulnerabilities


C:\Users\John\express-demo>type package.json
{
  "name": "express-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "config": "^3.1.0",
    "debug": "^4.1.1",      <====
    "express": "^4.16.4",
    "helmet": "^3.16.0",
    "joi": "^13.1.0",
    "morgan": "^1.9.1"
  }
}

The following code snippet illustrates how to use the debug module:

// **** enable logging (if in development) ****
if (app.get("env") === "development") {
  // **** create a write stream (in append mode) [d] ****
  const serverLogStream = fs.createWriteStream(
    path.join("./logs", "server.log"),
    {
      flags: "a"
    }
  );

  // **** setup the logger using a predefined format: tiny, short, dev, common, combined [e] ****
  app.use(morgan("common", { stream: serverLogStream }));

  // ???? inform the user what is going on ????
  // console.log("Morgan enabled...");
  debug("Morgan enabled...");           <====
}

To enable and / or disable Morgan you could define or undefined the specified value as illustrated by the following screen capture:

C:\Users\John\express-demo>set DEBUG=app:startup

C:\Users\John\express-demo>echo %DEBUG%
app:startup

C:\Users\John\express-demo>nodemon
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
  app:startup Morgan enabled... +0ms    <==== storage server listening on port: 4444 ... C:\Users\John\express-demo>set DEBUG=

C:\Users\John\express-demo>echo %DEBUG%
%DEBUG%

C:\Users\John\express-demo>nodemon
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
storage server listening on port:  4444 ...

You could enable multiple sets of debug statements in different or in the same module such as is illustrated in the following screen capture:

C:\Users\John\express-demo>set DEBUG=app:startup,app:db

C:\Users\John\express-demo>echo %DEBUG%
app:startup,app:db

C:\Users\John\express-demo>nodemon
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
  app:startup Morgan enabled... +0ms    <====
  app:db connecting to the db... +0ms   <==== storage server listening on port: 4444 ... C:\Users\John\express-demo>set DEBUG=app:*

C:\Users\John\express-demo>echo %DEBUG%
app:*

C:\Users\John\express-demo>nodemon
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
  app:startup Morgan enabled... +0ms    <====
  app:db connecting to the db... +0ms   <====
storage server listening on port:  4444 ...

So far the server responds to client requests using JSON. If you need your storage server to respond using HTML for some operations (e.g., display that the server is up and running) you could make use of a templating engine. There are many templating engines in npm as illustrated by the following templating engines:

* Pug (was: Jade)

o Mustache

o EJS

o Dust

In our storage server we will use pug so let’s install it as illustrated by the following screen capture:

C:\Users\John\express-demo>npm install pug
npm WARN express-demo@1.0.0 No description
npm WARN express-demo@1.0.0 No repository field.

+ pug@2.0.3
added 63 packages from 140 contributors and audited 266 packages in 27.478s
found 0 vulnerabilities


C:\Users\John\express-demo>type package.json
{
  "name": "express-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "config": "^3.1.0",
    "debug": "^4.1.1",
    "express": "^4.16.4",
    "helmet": "^3.16.0",
    "joi": "^13.1.0",
    "morgan": "^1.9.1",
    "pug": "^2.0.3"         <====
  }
}

Once installed we can make use of it in our code as illustrated here:

// **** set the template engine to use in this app (e.g., pug) ****
app.set("view engine", "pug");

// **** location of templates (default is: ./views) ****
app.set("views", "./views");

To use pug we need to create a template. We will create the template in the views folder in case we will need additional templates in the future. The following screen dump illustrates the contents of the index.pug template we just created:

C:\Users\John\express-demo>type .\views\index.pug
html
    head
        title= title
    body
        h1= message
        h2= message
        h3= message

Following is a run of the storage server:

C:\Users\John\express-demo>nodemon index.js
[nodemon] 1.18.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node index.js`
      NODE_ENV: development
app.get('env'): development
Application Name: Storage Server - Development
     Mail Server: CONDOR
       Mail Port: 1111
   Mail Password: 1234
storage server listening on port:  4444 ...

Received by Chrome:

<html>
    <head>
        <title>Storage Server</title>
    </head>
    
    <body cz-shortcut-listen="true">
 

<h1>storage server is up and running!!!</h1>



<h2>storage server is up and running!!!</h2>



<h3>storage server is up and running!!!</h3>


    </body>
</html>

I have split the code into some basic set of modules. The code for index.js follows:

// **** load express framework ****
const express = require("express");

// **** for morgan logger [a] ****
const fs = require("fs");

// **** local configuration module ****
const config = require("config");

// **** locad helmet module ****
const helmet = require("helmet");

// **** for validations (returns a class)****
const Joi = require("joi");

// **** set debug function(s) (typically one per module) ****
const debug = require("debug")("app:startup");

// **** morgan logger [b] ****
const morgan = require("morgan");

// **** for morgan to log to a file  ****
const path = require("path");

// **** our custom logging ****
const logger = require("./middleware/logger.js");

// **** our custom authentication ****
const authenticate = require("./middleware/authenticate");

// **** home route ****
const home = require("./routes/home");

// **** patient routes ****
const patients = require("./routes/patients");

// **** study routes ****
const studies = require("./routes/studies");

// **** create express application (server) ****
const app = express();

// **** set the template engine to use in this app (e.g., pug) ****
app.set("view engine", "pug");

// **** location of templates (default is: ./views) ****
app.set("views", "./views");

// **** display our environment ****
console.log(`      NODE_ENV: ${process.env.NODE_ENV}`);
console.log(`app.get('env'): ${app.get("env")}`);

// **** display our configuration ****
console.log(`Application Name: ${config.get("name")}`);
if (config.has("mail.host"))
  console.log(`     Mail Server: ${config.get("mail.host")}`);

if (config.has("mail.port"))
  console.log(`       Mail Port: ${config.get("mail.port")}`);

if (config.has("mail.password"))
  console.log(`   Mail Password: ${config.get("mail.password")}`);

// **** enable logging (if in development) ****
if (app.get("env") === "development") {
  // **** create a write stream (in append mode) [d] ****
  const serverLogStream = fs.createWriteStream(
    path.join("./logs", "server.log"),
    {
      flags: "a"
    }
  );

  // **** setup the logger using a predefined format: tiny, short, dev, common, combined [e] ****
  app.use(morgan("common", { stream: serverLogStream }));

  // ???? inform the user what is going on ????
  // console.log("Morgan enabled...");
  debug("Morgan enabled...");
}

// **** enable parsing of JSON objects by express (populates req.body) ****
app.use(express.json());

// **** parses incoming requests with urlencoded payloads (e.g., key=value&key=value) (populates req.body)
//      traditional approach; not use often ****
app.use(express.urlencoded({ extended: true }));

// **** serve static content from the specified folder (e.g., localhost:4444/readme.txt) ****
app.use(express.static("public"));

// **** enable helmet (middleware) ****
app.use(helmet());

// **** for any route like this, use this router ****
app.use("/", home);

// **** for any route like this, use this router ****
app.use("/api/patients", patients);

// **** for any route like this, use this router ****
app.use("/api/studies", studies);

// **** define the port to listen on ****
const port = process.env.STORAGE_SERVER_PORT || 4444;

// **** listen for requests ****
app.listen(port, () => {
  console.log(`storage server listening on port:  ${port} ...`);
});

The code for home.js follows:

// **** load express framework ****
const express = require("express");

// **** use router ****
const router = express.Router();

// **** for our custom logging ****
const logger = require("../middleware/logger.js");

// **** for our custom authentication ****
const authenticate = require("../middleware/authenticate.js");

// **** enable our logger middleware ****
router.use(logger);

// **** enable our authentication middleware ****
router.use(authenticate);

/*
 * route and handler (check if alive)
 */
router.get("", (req, res) => {
  // **** send simple text message ****
  // res.send("storage server is up and running!!!");

  // **** render back HTML page ****
  res.render("index", {
    title: "Storage Server",
    message: "storage server is up and running!!!"
  });
});

// ***** export the router ****
module.exports = router;

The code for patients.js follows:

router.use(logger);

// **** enable our authentication middleware ****
router.use(authenticate);

// **** patients in an array of objects (will later use MongoDB) ****
const patients = [
  { id: 1, name: "James Bond" },
  { id: 2, name: "Money Penny" },
  { id: 3, name: "John Canessa" },
  { id: 4, name: "Jane Doe" },
  { id: 5, name: "John Doe" }
];

/*
 * route and handle (delete patient by id)
 */
router.delete("/id/:id", (req, res) => {
  // **** extract the patient ID ****
  const patientID = req.params.id;

  // ???? log the patient ID ????
  console.log(`patientID: ${patientID}`);

  // **** look up the patient in the database (array) ****
  const patient = patients.find(p => p.id === parseInt(patientID));

  // **** patient ID not found ****
  if (!patient) return res.status("404").send(`patient ${patientID} NOT found`);

  // ???? ????
  console.log(`patient.id: ${patient.id} patient.name: ${patient.name}`);

  // **** find the patient index in the database (array) ****
  const index = patients.indexOf(patient);

  // ???? ????
  console.log(`index: ${index}`);

  // **** delete the patient from the database (array) ****
  patients.splice(index, 1);

  // **** return the patient info ****
  res.send(patient);
});

/*
 * route and handler (update specified patient record)
 */
router.put("/id/:id", (req, res) => {
  // **** extract the patient ID ****
  const patientID = req.params.id;

  // ???? log the patient ID ????
  console.log(`patientID: ${patientID}`);

  // **** look up the patient in the database ****
  const patient = patients.find(p => p.id === parseInt(patientID));

  // **** patient not found ****
  if (!patient)
    return res.status("404").send(`patientID: ${patientID} NOT found`);

  // **** ****
  // const result = validatePatient(req.body);
  const { error } = validatePatient(req.body); // result.error

  // **** check for validation error ****
  if (error) return res.status(400).send(error.details[0].message);

  // **** update the patient record ****
  patient.name = req.body.name;

  // **** return the updated patient record to client ****
  res.send(patient);
});

/*
 * route and handler (post new patient)
 */
router.post("/", (req, res) => {
  // **** (using object destructuring) ****
  const { error } = validatePatient(req.body);
  if (error) return res.status(400).send(error.details[0].message);

  // **** set the patient data ****
  const patient = {
    id: patients.length + 1, // MongoDB would generate an ID
    name: req.body.name // patient name provided by caller (enabled parsing of JSON objects)
  };

  // **** insert patient record into array (MongoDB) ****
  patients.push(patient);

  // ???? inform the user what is going on ????
  console.log(`patient.id: ${patient.id} patient.name ==>${patient.name}<==`); // **** send the patient record to the caller (caller needs to know patient ID) **** res.send(patient); }); /* * route and handler (list all patients) */ router.get("/", (req, res) => {
  // **** *****
  res.send(patients);
});

/*
 * route and handler (single patient by ID)
 */
router.get("/id/:id", (req, res) => {
  // **** extract the patient ID ****
  const patientID = req.params.id;

  // ???? log patient ID ????
  console.log(`patientID: ${patientID}`);

  // **** find the data in the array (in MongoDB) ****
  const patient = patients.find(p => p.id === parseInt(patientID));

  // **** send the patient back to the caller ****
  if (!patient) return res.status("404").send(`patient ${patientID} NOT found`);

  // **** send response ****
  res.send(patient);
});

/*
 * route and handler (single patient by name)
 */
router.get("/name/:name", (req, res) => {
  // **** get the patient name ****
  const patientName = req.params.name;

  // ???? display the patient name ????
  console.log(`patientName ==>${patientName}<==`); // **** find the data in the array (in MongoDB) **** const patient = patients.find(p => p.name === patientName);

  // **** send the patient record to the caller ****
  if (!patient)
    return res.status("404").send(`patient ==>${patientName}<== NOT found`); // **** send response **** res.send(patient); }); /* * route and handler (patient name) */ router.get("/name/:fn/:ln", (req, res) => {
  // **** extract the name of the patient ****
  var patientFirstName = req.params.fn;
  var patientLastName = req.params.ln;

  // ???? display the first and last name of the patient ????
  console.log(`patient name: ${patientFirstName}, ${patientLastName}`);

  // **** find the record in the array (in MongoDB) ****
  const patient = patients.find(
    p => p.name === patientFirstName + " " + patientLastName
  );

  // **** send the patient record to the caller ****
  if (!patient)
    return res
      .status("404")
      .send(`patient ==>${patientFirstName} ${patientLastName}<== NOT found`);

  // **** ****
  res.send(patient);
});

/*
 * validate patient request
 */
function validatePatient(patient) {
  // **** define validation schema ****
  const schema = {
    name: Joi.string()
      .min(6)
      .required()
  };

  // **** validate this patient request ****
  return Joi.validate(patient, schema);
}

// ***** export the router ****
module.exports = router;

The code for studies.js follows:

// **** load express framework ****
const express = require("express");

// **** use router ****
const router = express.Router();

// **** for our custom logging ****
const logger = require("../middleware/logger.js");

// **** for our custom authentication ****
const authenticate = require("../middleware/authenticate.js");

// **** enable our logger middleware ****
router.use(logger);

// **** enable our authentication middleware ****
router.use(authenticate);

/*
 * route and handler (studies per date)
 */
router.get("/date/:year/:month/:day", (req, res) => {
  // **** for ease of use ****
  const year = req.params.year;
  const month = req.params.month;
  const day = req.params.day;

  // ???? display request parameters ????
  console.log(`year: ${year} month: ${month} day: ${day}`);

  // **** send response ****
  res.send(req.params);
});

// ***** export the router ****
module.exports = router;

I will continue updating the storage server in the next couple weeks.

The entire code including the structure and some files which I might have missed discussing in this port can be found in my GitHub repository.

If you are interested in learning make sure you read and experiment. That is the best way to learn!

If you have comments or questions regarding this or any other post in this blog, or if you would like me to help with any phase in the SDLC (Software Development Life Cycle) of a product or service, please do not hesitate and leave me a note below. Requests for help will remain private.

John

Follow me on Twitter:  @john_canessa

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.