Node.js series Part 2. Create a Simple Blog App with Express.js

Node.js series Part 2. Create a Simple Blog App with Express.js

21. März 2020 Aus Von admin

Chapter 1: Introduction

In Part 1 of my node.js series we have learned how to easily build a web server with node.js on-board resources. Lastly we installed the Express.js module and configured simple routes as application end points that respond to http client requests. In this part 2 I would like to take a closer look at the most important functionalities of Express.js with you.

In connection with Express, terms like framework or web api keep coming up. Everyone says Express is a node.js web application framework which is absolutely true. But what does this mean? I would like to explain this here with my own words.

Developers install the Express module in the application root directory and integrate the module with the require() function in the application main file. This is done with the following command line.

var express = require('express')
var app = express()

The express module is first referenced to the variable express with the require() function and an express() function is exported from the express module. The express() function generates an express application which is stored in the variable app. From this moment on, the developer has a whole range of Express Features available that can be used in the web application that is to be built. The range of express features is provided by the Express Framework.

The features provided relate to the development of web applications. That means all these features from the Express Framework are designed to program the response behavior of a web server to requests from a client exactly as it is desired. These features are essentially about Routing- and so-called Middleware-functionalities.

The Routing tools in the Express Framework enable the developer to program the response behavior of the web server to the request for a special application end point. An application end point is a route that is e.g. defined as „/“ or „/about“. The response from the server should be different here. The developer can program the end points in a very simplified way so that the request for „/“ is answered by the server with the Home Page, the request for „/about“ is answered with the About Page. The Request- and Response Objects are very central to the routing tools. The developer can access the properties of these objects using various methods and thus program his end points accordingly.

To continue the example above the server immediately responds to the client’s request and delivers either the Home Page or the About Page. If the server should process after the request and before the response some code, we are talking about Middleware. Express offers various methods to access express internal middleware and then switch it between the request and the response. You can also program your own middleware functions, which can then be integrated into the application with the require() function.

All in all, Express is a very powerful framework that is all about programming web servers or web applications. The Express Features serve as an application programming interface between the client and server and that is why these features are also called Express web api. We will take a closer look at Express Routing and Express Middleware in this Part 2 of the node.js series.

Chapter 2: Express Routing

We start here with the directory structure of the application root directory from Part 1 of my node.js series and explain the content of the server.js file.

Patricks-MBP:express-basic patrick$ ls -l
total 56
drwxr-xr-x  52 patrick  staff   1664 21 Mär 08:13 node_modules
-rw-r--r--   1 patrick  staff  14292 21 Mär 11:28 package-lock.json
-rw-r--r--   1 patrick  staff    162 21 Mär 10:16 package.json
-rw-r--r--   1 patrick  staff   2435 21 Mär 17:47 server.js
Patricks-MBP:express-basic patrick$ 

The server.js file is the application main file and contain the code of the application. The package.json file contain meta data of the application or project such as which external modules or dependencies have been installed to be used by the application. The package-lock.json file contain the complete directory tree of all installed dependencies and finally the directory node_modules contain all dependencies or external modules or packages that have been installed.

At the end of part 1 we already installed the external module Express.js with npm install express –save and can now load this module in our application. The server.js file looks like this.

// server.js

// load http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// define the routes
app.get('/', (req, res) => {
  res.send('Hello, this is my home Page')
})

app.get('/about', (req, res) => {
  res.send('Hello, this is my about Page')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

We do the following with the code in the server.js file (from top to bottom).

  1. load modules
  2. define routes
  3. create the server
  4. define the port the server is listening
  5. log a success message to the console when all the code runs without problems

The Routing method

As you can see in the code we already defined 2 simple routes or application end points (URIs). These routes are defined with routing methods and have the following notation.

app.routingMethod( 'routingPath', routingHandler(req, res) => { ... })

In our server.js file, we created an app as an instance of the express class by calling the express() function right after we loaded the express module. Routing methods can now be attached to this app. Each routing method corresponds to an http verb such as GET, POST, PUT or DELETE. In our example, we will deal with get() and post() here. The routing method receives 2 parameters:

  1. routingPath: the path of the route end point starting from the root of the application „/“. Simply this is the path that the http request passed to the application. If the path passed meets an end point definition, the routingHandler code is executed.
  2. routingHandler: the routingHandler is a callback function that receives parameters. Here in the notation above the callback receives 2 parameters. The first parameter is the request object, the second the response object. Because the parameters req and res were passed, the code has access to the methods and properties of the request and response objects. We will talk about these Objects later in this article.

While the routingPath is a pretty logical thing, we’ll take a closer look at the routingHandler. The routingHandler contains code that is executed after a request and sends the response.

The routing handler

The routing handler is a callback function with req and res as parameters and process code after the requests and before sending the response. The response is sent with the res.send() method back to the client.

app.routingMethod( 'routingPath', routingHandler(req, res) => { 

... 

})

Here the res.send() method send a string back to the client.

// server.js

.........

// define the routes
app.get('/', (req, res) => {

  res.send('Hello, this is my Home Page')

})

......

The routing handler can also receive a function as a parameter.

app.routingMethod( 'routingPath', routingHandler(req, res, next) => { 

... 

})

Here the routing handler receive req and res but also the function next() as parameter. The next() function forward the processing to the next function(req, res). This final function sen the response to the client with the res.send() method.

// server.js

.........

app.get('/', (req, res, next) => {
  console.log('this request will be responded by next()');
  next();
}, function (req, res) {
  res.send('This is the response to the request from next()');
})

.........

As we have seen above the routing handler can receive req and res and functions as parameter.

You can also collect routing handler functions into an array and pass the array as parameter into the routing method.

app.routingMethod( 'routingPath', [array] )

So lets define 3 functions and pass them as array into the routing method.

// server.js

.........

var r1 = function (req, res, next) {
  console.log('r1 ! The request will be responded by r3');
  next();
}

var r2 = function (req, res, next) {
  console.log('r2 ! The request will be responded by r3');
  next();
}

var r3 = function (req, res) {
  res.send('r3 ! Hello, this is the response !');
}

app.get('/example', [r1, r2, r3]);

.........

In the example above there are 3 routing handler functions that are collected in an array. The array is passed to the routing method as a parameter. r1 and r2 each execute a console.log instruction and then pass the routing on to the next routing handler function with next(). r3 then returns the response to the client using the res.send() method.

You can collect functions in an array and insert them into the routing method before a last routing handler.

app.routingMethod( 'routingPath', [array], (req, res, next) => { 

... 

})

The last of these functions in the array forward the request to the last routing handler for processing. This last routing handler also contains a next() function and processes code first before finally answering the request.

// server.js

.........

var r1 = function (req, res, next) {
  console.log('r1 ! The request will be responded by final');
  next();
}

var r2 = function (req, res, next) {
  console.log('r2 ! The request will be responded by final');
  next();
}

var r3 = function (req, res, next) {
  console.log('r3 ! The request will be responded by final');
  next();
}

app.get('/example', [r1, r2, r3], (req, res, next) => {
    console.log('log 4 ! The request will be responded by final');
    next();
  }, function (req, res) {
      console.log('final ! This is the response to the request')
})

.........

The response method res.send()

The res.send(body) method send the http response back to the client. The body parameter can be

  • a string
  • an object
  • an array
  • a buffer object.
res.send() with string response

When res.send() body is a string the method sets the Content-Type to “text/html”.

res.send(body); body = String.

// server.js

.........

// define the routes
app.get('/', (req, res) => {

  res.send('Hello, this is my Home Page')

})

......

Start the server with node server.js and run curl -i localhost:3000 in another terminal window and check the output.

Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 27
ETag: W/"1b-AX0brvVSaJSaZMvUvejQv+1wFQA"
Date: Sat, 21 Mar 2020 16:50:26 GMT
Connection: keep-alive

Hello, this is my Home Page

Patricks-MBP:express-basic patrick$ 

res.send() with object or array response

When the body is an Array or an Object, the Content-Type is application/json.

res.send(body); body = Object.

// server.js

.........

// define the routes
app.get('/', (req, res) => {

  res.send( { error: 'something wrong in my app' } )

})

......

Start the server with node server.js and run curl -i localhost:3000 in another terminal window and check the output.

Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 37
ETag: W/"25-8fYR5HXYWF9HTHAAgk3niZYRep4"
Date: Sat, 21 Mar 2020 13:34:03 GMT
Connection: keep-alive

{"error":"something wrong in my app"}

Patricks-MBP:express-basic patrick$ 

res.send(body); body = Array.

// server.js

.........

// define the routes
app.get('/', (req, res) => {

  var array = [1, 2, 3]
  res.send(array)
  
})

......

Start the server with node server.js and run curl -i localhost:3000 in another terminal window and check the output.

Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 7
ETag: W/"7-nvUMyCrkdCefuOgolhQnArzLszo"
Date: Sat, 21 Mar 2020 13:35:57 GMT
Connection: keep-alive

[1,2,3]

Patricks-MBP:express-basic patrick$ 

res.send() with Buffer response

When the body is a Buffer object the Content-Type will be set to application/octet-stream.

res.send(body); body = Buffer.

// server.js

.........

// define the routes
app.get('/', (req, res) => {

 res.send(Buffer.from('<p>This is my buffer HTML</p>'))
  
})

......

Start the server with node server.js and run curl -i localhost:3000 in another terminal window and check the output.

Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/octet-stream
Content-Length: 29
ETag: W/"1d-TcgrXqCfH0MW//p+ASn/khisjHQ"
Date: Sat, 21 Mar 2020 13:39:23 GMT
Connection: keep-alive

<p>This is my buffer HTML</p>

Patricks-MBP:express-basic patrick$ 

Properties of the Request Object

The request object has properties that can be used on the request object. You can access various properties that have been provided with the request and process these data in your routing handler.

For example: You can access the request properties path and method with req.path and req.method. So when the client request i.e. the route „/users“ with HTTP GET then these properties will have the corresponing value.

// server.js

.........

app.get('/users', (req, res) => {

  path = req.path
  method = req.method

  console.log('Access / with GET. Path: ' +path +' and Method: ' +method);

  res.send('Hello, this is my Users Page')

})

.........

So start the server with node server.js in the server terminal and then access the route with curl -i localhost:3000/users in a different terminal. In the server terminal you see the following output.

Patricks-MBP:express-basic patrick$ node server.js
My node.js web server is alive and running at port 3000

Access /users with GET. Path: /users and Method: GET

For example: You have many users in your database and each user should have his own profile page in your web app. To provide such a profile page for each of your users you could create one route for each user. This mean you would have many routes and you must change your application by programming a new route whenever a new user join to your user population. This is not useful.

This can be avoided by using request parameters. Request parameters are defined in your route as follows.

// server.js

.........

app.get('/users/:name', (req, res) => {

  path = req.path
  method = req.method
  username = req.params.name

  console.log('Access /users/patrick with GET. Path: ' +path +' and Method: ' +method);
  
  console.log('Request Parameter name is: ' +username);

  res.send('Hello, this is my Home Page')

})

.........

Start the server with node server.js in the server terminal and then access the route with curl -i localhost:3000/users/patrick in a different terminal. In the server terminal you see the following output.

Patricks-MBP:express-basic patrick$ node server.js
My node.js web server is alive and running at port 3000

Access /users/patrick with GET. Path: /users/patrick and Method: GET

Request Parameter name is: patrick

Each of your users can access their individual profile page via a route /users/:name.

Suppose you want to show all users on the route /users in a list, you would expect that this would be possible without additional parameters when accessing the route /users. Unfortunately, this does not work in the current configuration.

Keep the server started in the server terminal and then access the route with curl -i localhost:3000/users in a different terminal. In the server terminal you see the following output.

Patricks-MBP:express-basic patrick$ curl -i localhost:3000/users
HTTP/1.1 404 Not Found
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 144
Date: Sat, 21 Mar 2020 06:51:23 GMT
Connection: keep-alive

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /users</pre>
</body>
</html>
Patricks-MBP:express-basic patrick$ 

The 404 HTTP status code tell you that the route /users does not exist and nothing has been found. To solve this issue you can add a question mark to the name in your route definition. This tells Express that the request parameter is optional and that the route /users can also be requested.

// server.js

.........

app.get('/users/:name?', (req, res) => {

  path = req.path
  method = req.method
  username = req.params.name

  console.log('Access /users/patrick with GET. Path: ' +path +' and Method: ' +method);
  
  console.log('Request Parameter name is: ' +username);

  res.send('Hello, this is my Home Page')

})

.........

Start the server again in the server terminal and then access the route with curl -i localhost:3000/users in a different terminal again.

Patricks-MBP:express-basic patrick$ curl -i localhost:3000/users
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 27
ETag: W/"1b-AX0brvVSaJSaZMvUvejQv+1wFQA"
Date: Sat, 21 Mar 2020 08:23:03 GMT
Connection: keep-alive

Hello, this is my Home Page

Patricks-MBP:express-basic patrick$ 

Chapter 3: Middleware in Express

As I mentioned in the introduction, middleware simply means that code is executed between the request and the response in order to parameterize the response if necessary. Lets have a look at the following route definition from above to show how routing in express sometimes behave like a middleware.

....

app.get('/example', [r1, r2, r3], (req, res, next) => {
    console.log('log 4 ! The request will be responded by final');
    next();
  }, function (req, res) {
      console.log('final ! This is the response to the request')
      
....

After a GET request on the route /example, the routing handler functions r1, r2 and r3 are executed. Console.log commands are executed from r1 to r3 and the request is forwarded with next(). Until then, however, no response is returned. The request is forwarded by r3 to the last routing handler function, which also first executes a console.log instruction before the request is then answered by the last routing handler function. From r1 to the response, the routing handler functions behaved like middleware.

It is also possible to load modules as middleware and always execute the code of the middleware when processing a request with any route and before the response.

We assume the following directory structure.

Patricks-MBP:express-basic patrick$ ls -l
total 64
drwxr-xr-x   8 patrick  staff    256 21 Mär 16:06 modules
drwxr-xr-x  52 patrick  staff   1664 21 Mär 08:13 node_modules
-rw-r--r--   1 patrick  staff  14292 21 Mär 11:28 package-lock.json
-rw-r--r--   1 patrick  staff    162 21 Mär 10:16 package.json
-rw-r--r--   1 patrick  staff   1635 21 Mär 09:22 server.js

Patricks-MBP:express-basic patrick$ 

A very good but simple example of how middleware modules work is the use of a simple console logger. To demonstrate this, I create a logger.js file in the modules directory.

The looger.js file in the modules directory has the following code.

// modules/logger.js

var logger = function(req, res, next) {

	var method = req.method
	var path = req.path

	console.log(method +' ' +path);
	next()

}

module.exports = logger;

The logger.js file contain a simple function logger() that will be exported using module.exports. Whenever a request is made for any route, the req.method and the req.path will be logged in the terminal and then the processing will be forwarded with the next() function. So first the request is passed to the application, then the console.log instruction is executed and then the request will be forwarded to the respective routing method.

The routes are defined in our main application file server.js. The server.js file in the modules directory has the following content.

// server.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// load own modules
const logger = require('./modules/logger')

// Integrate Middleware
app.use(logger);

// define the routes
app.get('/', (req, res) => {
  res.send('Hello, this is the Blog Home Page.')
})

app.get('/about', (req, res) => {
  res.send('Hello, this is the Blog About Page.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

In the server.js main application file I load the module and store the logger() function into the variable logger. The middleware is integrated in the code with app.use.

After that I start the server with node server.js. In another terminal window I use curl to call routes „/“ and „/about“.

Patricks-MBP:express-blog patrick$ curl -i localhost:3000/
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 34
ETag: W/"22-jCGRti3cySGTHqyUOi9QHFbFw2U"
Date: Sat, 21 Mar 2020 12:53:32 GMT
Connection: keep-alive

Hello, this is the Blog Home Page.

Patricks-MBP:express-blog patrick$ curl -i localhost:3000/about
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 35
ETag: W/"23-hKbwboK7j6DObmy7eAWiCel3bMQ"
Date: Sat, 21 Mar 2020 12:53:35 GMT
Connection: keep-alive


Hello, this is the Blog About Page.

Patricks-MBP:express-blog patrick$

In the first terminal window in which the server is running we see that the logging has worked for both routes. The console.log command of the logger middleware module was executed for both requests.

Patricks-MBP:express-blog patrick$ node server.js
My node.js web server is alive and running at port 3000

GET /
GET /about


Chapter 4: Create a Small Blog app with express

The small Blog app should be a web app without any HTML rendering and no input validation. It should simply show how we can make use of the things we learned so far. This blog will have blogs and users stored in a data file. We will create routes end points to add-, and remove users and blogs and we will create modules which contain the program logic to manipulate data.

note: You should never ever implement an application in production without any input validation. From a security perspective, this is absolutely unacceptable. I will deal with the topic input validation with Express in an extra post here on Digitaldocblog.

You can find the code discussed here in this chapter on my GitHub page.

First we create a new application root directory express-blog. In this directory we create the main application file blog.js and a package.json file with the touch command.

Patricks-MBP:2020-03-08 patrick$ mkdir express-blog
Patricks-MBP:2020-03-08 patrick$ ls -l
total 0
drwxr-xr-x  9 patrick  staff  288 21 Mär 07:34 express-basic
drwxr-xr-x  2 patrick  staff   64 21 Mär 07:18 express-blog
drwxr-xr-x  6 patrick  staff  192 21 Mär 09:26 node-basic

Patricks-MBP:2020-03-08 patrick$ cd express-blog

Patricks-MBP:express-blog patrick$ ls -l

Patricks-MBP:express-blog patrick$ touch blog.js
Patricks-MBP:express-blog patrick$ touch package.json

Patricks-MBP:express-blog patrick$ ls -l
total 0
-rw-r--r--  1 patrick  staff  0 21 Mär 07:19 blog.js
-rw-r--r--  1 patrick  staff  0 21 Mär 07:19 package.json

Patricks-MBP:express-blog patrick$ 

We open the package.json file in our editor and edit the file as follows.

{
  "name": "simple_blog",
  "version": "0.0.1",
  "main": "blog.js",
  "author": "Patrick Rottlaender"
}

We install express with npm install express –save –save-exact to ensure that we will install express in the latest version but without updates to the version by future installs.

Patricks-MBP:express-blog patrick$ npm install express --save --save-exact
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN simple_blog@0.0.1 No description
npm WARN simple_blog@0.0.1 No repository field.
npm WARN simple_blog@0.0.1 No license field.

+ express@4.17.1
added 50 packages from 37 contributors and audited 126 packages in 2.167s
found 0 vulnerabilities

Patricks-MBP:express-blog patrick$ ls -l
total 40
-rw-r--r--   1 patrick  staff      0 21 Mär 07:19 blog.js
drwxr-xr-x  52 patrick  staff   1664 21 Mär 07:33 node_modules
-rw-r--r--   1 patrick  staff  14287 21 Mär 07:33 package-lock.json
-rw-r--r--   1 patrick  staff    155 21 Mär 07:33 package.json

Patricks-MBP:express-blog patrick$ 

Then we create a data.json file and a modules directory.

Patricks-MBP:express-blog patrick$ touch data.json

Patricks-MBP:express-blog patrick$ mkdir modules

Patricks-MBP:express-blog patrick$ ls -l
total 40
-rw-r--r--   1 patrick  staff      0 21 Mär 07:19 blog.js
-rw-r--r--   1 patrick  staff      0 21 Mär 07:44 data.json
drwxr-xr-x   2 patrick  staff     64 21 Mär 07:51 modules
drwxr-xr-x  52 patrick  staff   1664 21 Mär 07:33 node_modules
-rw-r--r--   1 patrick  staff  14287 21 Mär 07:33 package-lock.json
-rw-r--r--   1 patrick  staff    155 21 Mär 07:33 package.json

Patricks-MBP:express-blog patrick$ 

The structure of the application root directory has now been created and we can start developing the blog app.

First step: First I want to write the initial code in the blog.js main application file. The code loads the express module which we installed earlier and starts the server on port 3000 of the local host. In addition, a home route is already defined.

// blog.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// define the routes
app.get('/', (req, res) => {
  res.send('Hello, this is the Blog Home Page.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My Blog server is running at port 3000')

To create users and blog data I should have 2 different modules. One that will create the users and the other one will create the blogs. Therefore I create 2 files in the modules directory.

Patricks-MBP:express-blog patrick$ touch modules/createUsers.js
Patricks-MBP:express-blog patrick$ touch modules/createBlogs.js

Patricks-MBP:express-blog patrick$ ls -l modules
total 0
-rw-r--r--  1 patrick  staff  0 21 Mär 08:02 createBlogs.js
-rw-r--r--  1 patrick  staff  0 21 Mär 08:02 createUsers.js

Patricks-MBP:express-blog patrick$ 

Second step: In the data.json file I will store the user and blog data. The data file is currently empty but will have the following structure.

{
	"users":[{name:..., lastname:..., email:..., id:... },{...},...],
	"blogs":[{title:..., author:..., date:..., id:... },{...},...]

}

There is a json data object with the keys users and blogs. These keys have an array value with objects as array elements.

i,j = 0, 1, 2 ...

data.users[i]
data.blogs[j]

I will now program the 2 modules usersAdd and blogsAdd in the createUsers.js and createBlogs.js files. These files contain functions that initially create user or blog records or add corresponding user or blog records.

The createUsers.js file contains the following code.

// modules/createUsers.js

// load node fs module
const fs = require('fs')

const usersAdd = function(req, res) {

    var dataExp = fs.readFileSync('./data.json', 'utf8')

    if(!dataExp) {
      console.log('no data available');
      var data = {}
      data.users = []

      var newUserName = req.body.name
      var newUserLastname = req.body.lastname
      var newUserEmail = req.body.email
      var newUserId = 100

      var newUser = {
        name: newUserName,
        lastname: newUserLastname,
        email: newUserEmail,
        id: newUserId
      }

    data.users.push(newUser)

    dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('Intial User created');
        });

    } else {
      var data = JSON.parse(dataExp)

      if(!data.users) {
        console.log('no users are available')
        data.users = []

        var newUserName = req.body.name
        var newUserLastname = req.body.lastname
        var newUserEmail = req.body.email
        var newUserId = 100

        var newUser = {
          name: newUserName,
          lastname: newUserLastname,
          email: newUserEmail,
          id: newUserId
        }

      data.users.push(newUser)

      dataToFile = JSON.stringify(data)

        fs.writeFile('./data.json', dataToFile, function(err) {
          if (err) throw err;
          console.log('Intial User created');
          });

      } else {
        console.log('users are available')
        var newID
        var minID = data.users[0].id

        for (i = 0; i < data.users.length; i++) {
          if (data.users[i].id >= minID) {
              newID = data.users[i].id + 100
          }
        }

        var newUserName = req.body.name
        var newUserLastname = req.body.lastname
        var newUserEmail = req.body.email
        var newUserId = newID

        var newUser = {
          name: newUserName,
          lastname: newUserLastname,
          email: newUserEmail,
          id: newUserId
        }

      data.users.push(newUser)

      dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('User added');
        });
      }
  }
}

module.exports = usersAdd;

The fs module is loaded first. This is a node internal module which is used to read files and also to write data into a file. Then we create the usersAdd function that will be exported with module.exports.

The usersAdd function read the data.json file with fs.readFileSync and store the received data into the dataExp variable. If dataExp is empty we have an empty data file and must first create the empty data Object and store this into the variable data. Then we create the users key on the data object with data.users and assign an empty array.

From the request body we receive the name, lastname and email and store these data into new user variables. For the initial user entry we define the id 100.

note: Later in this tutorial we will see the POST route definition /createusers in the application main file blog.js. This POST route will call usersAdd with the parameters req and res so that we have access to the req.body object in createUsers.js. We will see this later.

We create a newUser object with the keys like name, lastname, email, id and assign the new user variables we received from the request body to them as values. Then we push the newUser object into the array of the users key (created with data.users = [ ] at the beginning) which is linked to the data object that we created at the beginning with data = .

data.users.push(newUser)

After this we have the following JavaScript data object.

{

	"users": { [ {name:..., lastname:..., email:..., id:... } ] }

}

With JSON.stringify() we take the JavaScript data object, create a JSON string out of it and store this string into the dataToFile variable. With fs.writeFile we write the string into the so far empty data.json file.

note: If you use JavaScript to develop applications, JavaScript objects have to be converted into strings if the data is to be stored in a database or a data file. The same applies if you want to send data to an API or to a webserver. The JSON.stringify() function does this for us.

So far we know what the code does in case the data.json file is completely empty and the first if condition is true (if(!dataExp)). And now lets see what happens what the code does in case the first if condition is false which means that the data.json file already has data because that is the case if the variable dataExp is not empty.

As we have seen we store a JSON string in the data file. So when we read the file and store the data into the variable dataExp we have JSON string data in that variable. To process these data in our JavaScript code we must parse this JSON so that a JavaScript object is created from the JSON string. This is what we do with the JSON.parse() function.

Here we store the JavaScript object into the variable data.

var data = JSON.parse(dataExp)

Since our code is designed to store only one data object in the data.json file, the variable data is our data object. So when we have the data object we can ask if users are already existing.

Therefore we use the if condition to check if the key data.users exist.

In case the condition if(!data.users) is true, the key data.users is not existing which means that no users are available so far. At his point we create a data.users key and assign an empty array. In the following, a newUser object is created in the same way as described above and inserted into the array using the data.users.push() method. Now the data object has an additional key data.users and this key has a newUser Object as value. Now we can convert this entire data object into a string again and completely overwrite the data.json file.

In case the condition if(!data.users) is false, the key data.users is existing and users are available. If users exist the individual user records naturally have existing ids. So we first have to create an algorithm to get a new user ID for the new user.

We first create a variable newID and a variable minID. minID corresponds to the userID which has to be assigned at least since it corresponds to the userID of our first user data object. minID must be the id value of the first user data object record. Since the user objects are stored in an array, this must correspond to the user object at position zero.

var minID = data.users[0].id

newID should be plus 100 larger than the id of the last found user object.

That is why we iterate through all user objects, adding the value 100 to the id value of the last user object found. Finally, we take the id value of the last user object and add 100 and assign this value to newID.

In the following again a newUser object is created, the entire data object is converted into a string and stored into the data.json file.

The code for the blogsAdd function is implemented exactly according to the same scheme. The only difference is that the blog id only grows by 1.

The createBlogs.js file contains the following code.

// modules/createBlogs.js

// load node fs module
const fs = require('fs')

const blogsAdd = function(req, res) {

    var dataExp = fs.readFileSync('./data.json', 'utf8')

    if(!dataExp) {
      console.log('no data available')
      data = {}
      data.blogs = []

      var newBlogTitle = req.body.title
      var newBlogAuthor = req.body.author
      var newBlogDate = req.body.date
      var newBlogId = 1

      var newBlog = {
        title: newBlogTitle,
        author: newBlogAuthor,
        date: newBlogDate,
        id: newBlogId
      }

    data.blogs.push(newBlog)

    dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('Intial Blog created');
        });

    } else {
      var data = JSON.parse(dataExp)

      if (!data.blogs) {
        console.log('no blog are available')
        data.blogs = []

        var newBlogTitle = req.body.title
        var newBlogAuthor = req.body.author
        var newBlogDate = req.body.date
        var newBlogId = 1

        var newBlog = {
          title: newBlogTitle,
          author: newBlogAuthor,
          date: newBlogDate,
          id: newBlogId
        }

      data.blogs.push(newBlog)

      dataToFile = JSON.stringify(data)

        fs.writeFile('./data.json', dataToFile, function(err) {
          if (err) throw err;
          console.log('Intial blog created');
          });

      } else {
        console.log('blogs are available');
        var newID
        var minID = data.blogs[0].id

        for (i = 0; i < data.blogs.length; i++) {
          if (data.blogs[i].id >= minID) {
              newID = data.blogs[i].id + 1
          }
        }

        var newBlogTitle = req.body.title
        var newBlogAuthor = req.body.author
        var newBlogDate = req.body.date
        var newBlogId = newID

        var newBlog = {
          title: newBlogTitle,
          author: newBlogAuthor,
          date: newBlogDate,
          id: newBlogId
        }

      data.blogs.push(newBlog)

      dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('blog added');
        });
      }
  }
}

module.exports = blogsAdd;

Third Step: The blog.js file needs to be adjusted. First we integrate middleware that allows us to parse incoming http requests with urlencoded payloads.

app.use(express.urlencoded({ extended: false }))

Then the two modules must be loaded into the blog.js file with the require() function. Therefore we require the files createUsers.js and createBlogs.js.

const createUsers = require('./modules/createUsers')
const createBlogs = require('./modules/createBlogs')

note: I would like to explain the topic require() of modules again at this point. The files createBlogs.js and createUsers.js contain the usersAdd and blogsAdd functions which are each exported with module.exports. The module.exports instruction means that these functions can be called in other files of the application if the createBlogs.js and createUsers.js files are loaded with require(). usersAdd and blogsAdd have no return value and therefore the functions themselves are saved in the variables createBlogs and createUsers in blog.js. If e.g. createUsers(req, res) is called in a POST route, the usersAdd function is called and req an res are passed as parameters.

As we can see in the code of the two modules above, the values for the initial creation or the addition of user- or blog objects are passed to the functions usersAdd or blogsAdd via the request body. For this, POST routes /createusers and /createblogs must be created in the blog.js file. These POST routes call the functions usersAdd or blogsAdd via createUsers() or createBlogs().

The blog.js file contains the following code.

// blog.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }))

// load own modules
const createUsers = require('./modules/createUsers')
const createBlogs = require('./modules/createBlogs')

// define the routes
app.get('/', (req, res) => {
  res.send('Hello, this is the Blog Home Page.')
})

app.post('/createusers', (req, res) => {
  createUsers(req, res)
  res.send('User has been added successfully.')
})

app.post('/createblogs', (req, res) => {
  createBlogs(req, res)
  res.send('Blog has been added successfully.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

Now we are able to add blogs and users.

note: Here in this application we have no User interface to enter the user variables like name, lastname, email or the blog variables like title, author and date. With this application you cannot enter the data in a form and then transfer it to the application routes /createusers or /createblogs using HTTP POST. This is where Postman comes in. With Postman you can simulate a form by manually entering this data in the HTTP body of the HTTP POST request. Postman check whether the POST request was successful or not. I will not go into Postman at this point. So pls. check the documentation on the Postman website.

But there is still no way to remove data from the data.json. We now want to show this using the example of how to remove users.

The module which should take over exactly this functionality is the function usersRemove() which I will write in the file modules/usersRemove.js. To do this, we first create an empty file usersRemove.js in the modules directory.

Patricks-MBP:express-blog patrick$ pwd
/Users/patrick/Software/dev/node/myArticles/2020-03-08/express-blog
Patricks-MBP:express-blog patrick$ ls -l modules
total 24
-rw-r--r--  1 patrick  staff  2285 21 Mär 17:18 createBlogs.js
-rw-r--r--  1 patrick  staff  2319 21 Mär 17:17 createUsers.js
-rw-r--r--  1 patrick  staff   652 21 Mär 06:50 removeUsers.js

Patricks-MBP:express-blog patrick$ 

The removeUsers.js file contains the following code.

// modules/removeUsers.js

// load node fs module
const fs = require('fs')

const usersRemove = function(req, res) {
  var dataExp = fs.readFileSync('./data.json', 'utf8')
  var data = JSON.parse(dataExp)

  var delUserID = req.body.id - 0
  
  var elementFound = function(user) {
    return user.id === delUserID
  }

  var index = data.users.findIndex(elementFound)

  data.users.splice(index, 1)

  dataToFile = JSON.stringify(data)
 
  fs.writeFile('./data.json', dataToFile, function(err) {
    if (err) throw err;
    console.log('userID removed');
    });
 
}

module.exports = usersRemove;

We first load the node fs module again and then define the function usersRemove() which is then exported with module.exports at the end of the code. The function usersRemove() receive req and res as parameters, read the data.json file and parse the read data into a JavaScript object represented by the variable data.

To show you the data object I just run the script as follows.

// modules/removeUsers.js

// load node fs module
const fs = require('fs')

const usersRemove = function(req, res) {
  var dataExp = fs.readFileSync('./data.json', 'utf8')
  var data = JSON.parse(dataExp)
  
  console.log(data)

}

module.exports = usersRemove;

When you check your console you can see that we saved all the json-data from the file in the variable data. We see a JavaScript object that can be used in our code.

{
  blogs: [
    {
      title: 'First Title',
      author: 'First test author',
      date: '2020-03-28',
      id: 1
    },
    {
      title: 'Second Title',
      author: 'Second test author',
      date: '2020-03-28',
      id: 2
    }
  ],
  users: [
    {
      name: 'Oskar',
      lastname: 'Rottländer',
      email: 'o.rottlaender@test.com',
      id: 100
    },
    {
      name: 'Patrick',
      lastname: 'Rottländer',
      email: 'p.rottlaender@test.com',
      id: 200
    },
    {
      name: 'Carolin',
      lastname: 'Rottländer',
      email: 'c.rottlaender@test.com',
      id: 300
    }
  ]
}

In the code we take the userID from the req.body and store it into the variable delUserID. The value from the req.body is a string. As we operate with userIDs as numbers in our json data file we must convert the string into a number. This is done with „- 0“. Here we subtract the number value zero and tell JavaScript that the delUserID is a number instead of a string.

Then we must find the user object that need to be deleted. user objects are elements in the data.users array. Consequently we must find the user object with a userID from the req.body in the data.users array because this userID identify the user object that should then be deleted and therefore removed from the data.users array.

The callback function elementFound return true when the userID from the rep.body is equal to a userID of a user object in the data.users array.

The method data.users.findIndex() return the index of the first user object found in the data.users array where the userID from the rep.body is equal to the userID of the user object.

The data.users.splice(index, 1) remove one user object at the position of index. As the findIndex() method before returned the exact position of the user object that we want to delete the splice() method here ultimately removes the user object.

The complete data object consisting of blog objects and user objects is now reduced by one user object. the reduced data object is converted as a whole into a string and written again into the file data.json using the fs.writeFile() method.

The blogsRemove() function works identically to the usersRemove() function explained above. The file removeBlogs.js in the module directory therefore looks like this.

// modules/removeBlogs.js

// load node fs module
const fs = require('fs')

const blogsRemove = function(req, res) {
  var dataExp = fs.readFileSync('./data.json', 'utf8')
  var data = JSON.parse(dataExp)

  var delBlogID = req.body.id - 0

  var elementFound = function(blog) {
    return blog.id === delBlogID
  }

  var index = data.blogs.findIndex(elementFound)

  data.blogs.splice(index, 1)

  dataToFile = JSON.stringify(data)

  fs.writeFile('./data.json', dataToFile, function(err) {
    if (err) throw err;
    console.log('blogID removed');
    });
}

module.exports = blogsRemove;

In the code of the two removeUsers.js and removeBlogs.js modules above, the values for the to be removed user- or blog objects are passed to the functions usersRemove() or blogsRemove() via the request body. For this, POST routes /removeusers and /removeblogs must be created in the blog.js file. These POST routes call these functions usersRemove() or blogsRemove() which are defined in the modules removeUsers.js and removeBlogs.js. These modules must also be loaded into the blog.js file with the require() function.

The blog.js file now contains the following code.

// blog.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }))

// load own modules
const createUsers = require('./modules/createUsers')
const removeUsers = require('./modules/removeUsers')
const createBlogs = require('./modules/createBlogs')
const removeBlogs = require('./modules/removeBlogs')

// define the routes
app.get('/', (req, res) => {
  res.send('Hello, this is the Blog Home Page.')
})

app.post('/createusers', (req, res) => {
  createUsers(req, res)
  res.send('User has been added successfully.')
})

app.post('/removeusers', (req, res) => {
  removeUsers(req, res)
  res.send('User has been successfully removed.')
})


app.post('/createblogs', (req, res) => {
  createBlogs(req, res)
  res.send('Blog has been added successfully.')
})

app.post('/removeblogs', (req, res) => {
  removeBlogs(req, res)
  res.send('Blog has been successfully removed.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

Fourth step: Our application can currently create users and create blogs as well as delete blogs and users. Thats fine so far. Now another functionality should be added, namely the display of blogs. This is to be implemented in such a way that the GET request for one route sends different responses back to the server depending on the request parameters provided with the request.

What does this mean ?

For example: We have various blogs in our data file, all of them have been created for specific dates. We want to see all blogs, but also blogs from a certain year or month or blogs that were created on a specific date.

You want to access all blogs using this route.

app.get('/blog', (req, res) => {
  	....
  
  })

And you want to access all blogs in 2020 using this route.

app.get('/blog/2020', (req, res) => {
  	....
  
  })

And so on.

We can do this much more elegantly using request parameter. We create the following GET route in our blog.js file.

// blog.js

.....

app.get('/blog/:year?/:month?/:day?', (req, res) => {
  	....
  
  })
  
.....

The question mark indicates that the parameters year, month and day are optional and can also be omitted when requesting.

We have defined a route that should give different responds depending on the request parameters. That is why we first have to evaluate the request parameters in the route definition and then define conditions as to how the different inquiries should be answered.

// blog.js

.....

app.get('/blog/:year?/:month?/:day?', (req, res) => {

  url = req.url
  year = req.params.year
  month = req.params.month
  day = req.params.day

    if (url == '/blog') {
      	...    
    }

    else if (url == '/blog/'+year) {
 		...
    }

    else if (url == '/blog/'+year+'/'+month) {
    	 ...
    }

    else if (url == '/blog/'+year+'/'+month+'/'+day) {
     	...
    }

    else {
      res.status(404).send('Not Found')
    }

})

.....

I will come back to the main application file blog.js later.

Our goal is to display blog posts. Depending on the route, we want to show (1) all blogs or (2) all blogs of a year or (3) all blogs of a year and a month or (4) all blog posts from a specific day.

To collect all these data from our data.json file we basically need 4 new functions to perform those queries. Therefore I create a new queryModule in the modules directory. This queryModule contain all these 4 query functions and all of them will be made available within our application. The notation of the code in the queryModule file is slightly different than in the previous modules.

The reason is that we previously had one file as a module in the modules directory and only one function in that file. The export value was the function. These modules looked like this.

// modules/previousModule.js

var myFunction = function() {
	....
}
module.exports = myFunction

In our main application file we load the previousModule with the require function and store a function into the variable previousModule. We can call the function in the main application file directly with previousModule().

// mainApplicationFile.js

.....

const previousModule = require('./modules/previousModule')

.....

previousModule()

....


Here in the queryModule (which is our queryBlogs.js file in modules directory) we collect 4 functions but we do not export them. Instead we use module.exports to export an object with 4 keys. Each of these keys has exactly one value and that is the respective function.

// modules/queryModule.js

module.exports = {

	function1: function() {
				....	
			}
	
	function2: function() {
				....	
			}
			
	function3: function() {
				....	
			}
			
	function4: function() {
				....	
			}

}

In our main application file we load the queryModule with the require function and store an object into the variable queryModule. We can call the functions in the main application file via the object.key notation.

// mainApplicationFile.js

.....

const queryModule = require('./modules/queryModule')

.....

queryModule.function1()

queryModule.function2()

queryModule.function3()

queryModule.function4()

....

When we take a look at our new queryModule which is the queryBlogs.js file in the modules directory you see that we have an object with 4 different keys and those keys have a function() as values.

  • queryAllBlogs
  • queryBlogsYear
  • queryBlogsYearMonth
  • queryBlogsYearMonthDay

There is another important difference to the previous modules. Those functions in queryBlogs.js return each a value and this value can be used later in our main application file blog.js. This value is an array of blog posts that should be displayed when a certain route has been requested.

So the queryBlogs.js file looks as follows. I will explain the queryAllBlogs and the queryBlogsYear in detail. The other 2 functions queryBlogsYearMonth and queryBlogsYearMonthDay are schematically the same.

// modules/queryBlogs.js

// load node fs module
const fs = require('fs')

module.exports = {

  queryAllBlogs: function() {

    var allBlogs = []

    var dataExp = fs.readFileSync('./data.json', 'utf8')
    var data = JSON.parse(dataExp)

    for(i = 0; i < data.blogs.length; i++) {
      var dataSet = {
        title: data.blogs[i].title,
        author: data.blogs[i].author,
        date: data.blogs[i].date
      }
      allBlogs.push(dataSet)
    }
      return allBlogs
  },

  queryBlogsYear: function(allBlogs) {
    var yearBlogs = []
    for (i = 0; i < allBlogs.length; i++) {

      blogDate = allBlogs[i].date
      parsedBlogDate = new Date (Date.parse(blogDate))
      parsedBlogDateYear = parsedBlogDate.getFullYear()

      if (year == parsedBlogDateYear) {
        var dataset = {
          title: allBlogs[i].title,
          author: allBlogs[i].author,
          date: blogDate
        }
        yearBlogs.push(dataset)
      }
  }
  return yearBlogs
  },

  queryBlogsYearMonth: function(allBlogs) {
    var yearMonthBlogs = []
    for (i = 0; i < allBlogs.length; i++) {

      blogDate = allBlogs[i].date
      parsedBlogDate = new Date (Date.parse(blogDate))
      parsedBlogDateMonth = (parsedBlogDate.getMonth() + 1)
      parsedBlogDateYear = parsedBlogDate.getFullYear()

      if (year == parsedBlogDateYear && month == parsedBlogDateMonth) {
        var dataset = {
          title: allBlogs[i].title,
          author: allBlogs[i].author,
          date: blogDate
        }
        yearMonthBlogs.push(dataset)
      }
  }
  return yearMonthBlogs
  },

  queryBlogsYearMonthDay: function(allBlogs) {
    var yearMonthDayBlogs = []
    for (i = 0; i < allBlogs.length; i++) {

      blogDate = allBlogs[i].date
      parsedBlogDate = new Date (Date.parse(blogDate))
      parsedBlogDateDay = parsedBlogDate.getDate()
      parsedBlogDateMonth = (parsedBlogDate.getMonth() + 1)
      parsedBlogDateYear = parsedBlogDate.getFullYear()

      if (year == parsedBlogDateYear && month == parsedBlogDateMonth && day == parsedBlogDateDay) {
        var dataset = {
          title: allBlogs[i].title,
          author: allBlogs[i].author,
          date: blogDate
        }
        yearMonthDayBlogs.push(dataset)
      }
    }
    return yearMonthDayBlogs
  }

}

Our data is stored as a string in a data.json file. Every operation of our application in relation to the data requires that we have access to a JavaScript data object. So if we want to search or select data to display only a part of it in the application, e.g. only all blog posts from a certain year, it is still necessary to have read the entire data from the data file in beforehand. In a larger project with a larger amount of data, this approach is definitely not the right one.

note: With larger amounts of data, it makes sense to use a database such as MongoDB because we can then search for specific values directly in the database and it is not necessary to retrieve all data from the database. I will show you the use of a MongoDB in a node.js project in another article at a later time. For simplicity’s sake, let’s stick here in this article to a file-based approach.

Therefore the queryAllBlogs function serves as the basis for our other queries for displaying blogs. This function first defines an empty array allBlogs. Then, as already described above, the data is read completely from the data file with fs.readFileSync and converted into a JavaScript object with JSON.parse. Now we have access to blog posts with data.blogs. However, this is not entirely true. As we have already seen above, data.blogs is a key in the data object and this key has an array as value. Strictly speaking, this array contains elements that are the blog posts.

In any case, with the for loop we iterate through all elements of the array using data.blogs[i] so that each individual blog data record can be read from the data file and attached with the allBlogs.push() method as an dataSet object to the allBlogs array that was defined first. The function queryAllBlogs() then return allBlogs back to the caller. allBlogs contain all the dataSet objects. The allBlogs array looks like the following.

[
	{
	
	title: valueTitle,
	author: valueAuthor,
	data: valueDate
	
	},
	
	{
	
	...
	
	},
	
	...

]

The function queryBlogsYear take the allBlogs array as parameter (like any other function queryBlogs* defined in the module). This is because we want to select data from it in the queryBlogsYear function and therefore all blog posts must be available in the function scope.

First we create the empty array yearBlogs. This array will contain at the end of the function code all the data that should be returned later by the queryBlogsYear function.

Then we iterate through the allBlogs array using the for loop.

In the for loop, we first read at the value of the blog date with allblogs[i].date and store the value in the variable blogDate. In the data file the date is saved with ISO 8601 date format (YYYY-mm-dd). It is highly recommended to always save the date in this format since ISO 8601 is the date format preferred by JavaScript and so all other operations with the date are then made easier. In order to work well with the date string yyyy-mm-dd in JavaScript, this string date must first be converted into a number using. Then the number will be converted into a date object using the Date.parse() method.

note: Date.parse() receive the string date in ISO 8601 format yyyy-mm-dd and returns a number that represents the milliseconds between January 1st, 1970 00:00:00 UTC. This number is unique and is required to create the date object using the new Date() method. With this date object we have with JavaScript access to the year, month or day of the given date object.

Therefore, in our code Date.parse() is passed directly as parameter into the new Date() method so that the required date object can be generated directly from the returned number. The date object is stored in the variable parsedBlogDate.

Because parsedBlogDate represents an object, the parsedBlogDate.getFullYear() method gives us access to the year passed with the date. The year is stored in the parsedBlogDateYear variable.

note: Later we will use parsedBlogDate.getMonth() to retrieve the month from the date object. This method returns 0 for January and 11 for December. It is therefore necessary to add „+1“ to get the usual values 1 for January and 12 for December. And we will use parsedBlogDate.getDate() to retrieve the day of the month as 1 for the first day and max 31 for the last day of the respective month.

Then we compare in the if loop the year passed in the request (year = req.params.year, variable year is defined in blog.js) with the years of all blog posts. We only generate a dataset where the year from the request matches the year of the blog post. The array yearBlogs created in this way is then returned to the caller as a return value.

I will now come back to main application file blog.js and the specific route definition where we evaluate the request parameters and provide different responses. With this route we send, depending on the request parameters, the following responses.

  • all blog posts – res.send(allBlogs)
  • blog posts of given year – res.send(blogsYear)
  • blog posts of a give year and month – res.send(blogsYearMonth)
  • blog posts of a given date – res.send(blogsYearMonthDay)
// blog.js

....

const queryBlogs = require('./modules/queryBlogs')

....

app.get('/blog/:year?/:month?/:day?', (req, res) => {

  url = req.url
  year = req.params.year
  month = req.params.month
  day = req.params.day

  var allBlogs = queryBlogs.queryAllBlogs()

    if (url == '/blog') {
      if (allBlogs.length == 0) {
        res.send('no post found')
      } else {
        res.send(allBlogs)
      }
    }

    else if (url == '/blog/'+year) {

      const blogsYear = queryBlogs.queryBlogsYear(allBlogs)

      if(blogsYear.length == 0) {
        res.send('no post found for this year')
      } else {
        res.send(blogsYear)
      }

    }

    else if (url == '/blog/'+year+'/'+month) {

      const blogsYearMonth = queryBlogs.queryBlogsYearMonth(allBlogs)

      if(blogsYearMonth.length == 0) {
        res.send('no post found for this year and month')
      } else {
        res.send(blogsYearMonth)
      }

    }

    else if (url == '/blog/'+year+'/'+month+'/'+day) {

      const blogsYearMonthDay = queryBlogs.queryBlogsYearMonthDay(allBlogs)

      if(blogsYearMonthDay.length == 0) {
        res.send('no post found for this year and month and day')
      } else {
        res.send(blogsYearMonthDay)
      }

    }

    else {
      res.status(404).send('Not Found')
    }

})

....

We have defined a route that give different responds depending on the request parameters. We want to see all blogs when the request url == /blog, we see all blogs i.e. of a given year 2019 when the request url == /blog/2019 and so an.

First we load the queryBlogs module with the require() function and save the object in the variable queryBlogs (remember ? queryBlogs return an object of functions).

In the route definition we take the request parameters passed with the request and save them in the variables url, year, month and day. Then we access the queryAllBlogs() function defined in the queryBlogs module with queryBlogs.queryAllBlogs(). This function return all blog posts stored in an array. This array returned by the function will be stored in the variable allBlogs.

Then we create if and else if conditions to define the response behavior of our route definition in our application.

The first if condition is true in case the requested url is equal to /blog. The following if condition is true when the allBlogs array is empty. Then the response is no posts found res.send(’no post found‘). When this if condition is true blog posts have been found in the allBlogs array and this array is not empty. In this case all the data from the allBlogs array will be send back to the caller with res.send(allBlogs).

The first else if condition is true in case the requested url is equal to /blog/year (i.e. /blog/2019). When this else if condition is true we call the queryBlogsYear() function in our queryBlogs module and pass the allBlogs array as parameter. We call the function using queryBlogs.queryBlogsYear(allBlogs) and store the returned value in the array variable blogsYear.

If the blogsYear array is empty the following if condition is true. This mean no blog posts have been found for the requested year in allBlogs. Then the response is no posts found for this year res.send(’no post found for this year‘). When this if condition is false blog posts have been found for the requested year and the blogsYear array is not empty. Then all these data from the blogsYear array will be send back to the caller with res.send(blogsYear).

The second else if condition is true in case the requested url is equal to /blog/year/month (i.e. /blog/2019/04). When this else if condition is true we call the queryBlogsYearMonth() function in our queryBlogs module and pass the allBlogs array as parameter. We call the function using queryBlogs.queryBlogsYearMonth(allBlogs) and store the returned value in the array variable blogsYearMonth.

If the blogsYearMonth array is empty the following if condition is true. This mean no blog posts have been found for the requested year and month in allBlogs. Then the response is no posts found for this year and month res.send(’no post found for this year and month‘). When this if condition is false blog posts have been found for the requested year and month and the blogsYearMonth array is not empty. Then all these data from the blogsYearMonth array will be send back to the caller with res.send(blogsYearMonth).

The third else if condition is true in case the requested url is equal to /blog/year/month/day (i.e. /blog/2019/04/10). When this else if condition is true we call the queryBlogsYearMonthDay() function in our queryBlogs module and pass the allBlogs array as parameter. We call the function using queryBlogs.queryBlogsYearMonthDay(allBlogs)and store the returned value in the array variable blogsYearMonthDay.

If the blogsYearMonthDay array is empty the following if condition is true. This mean no blog posts have been found for the requested date in allBlogs. Then the response is no posts found for this year and month and day res.send(’no post found for this year and month and day‘). When this if condition is false blog posts have been found for the requested date and the blogsYearMonth array is not empty. Then all these data from the blogsYearMonthDay array will be send back to the caller with res.send(blogsYearMonthDay).

The first if condition is false in case the requested url does not match any of the defined request parameters (/blog/:year?/:month?/:day?). Then the else response is res.status(404).send(‚Not Found‘).

Summary and Outlook

In this Part 2 of my node.js series I explained some very important details of Express and how express can be used in web application development. Based on what we already learned about Express in Part 1, we looked at Express Routing in detail and how you can use middleware in Express relatively easily. With the knowledge we learned, we then built a simple blog web application and thus applied the knowledge.

This blog app has all the data in a file and no HTML frontend or no HTML template rendering is used. Express is ideally suited for the use of a database like MongoDB and also for the use of HTML templates to present the content. In the next Part 3 of my node.js series I will show you how our blog app uses a MongoDB database instead of the data file. Then we will continue to develop the blog app and implement HTML rendering.