Jetbatsa stands for Just Enough To Be Able To Speak About. This is the code name for posts that are check lists or quick notes for myself while I explore some topic. This posts describes the basic steps to get a NodeJS/Express application up and running. The application we’ll be building exposes some unrelated endpoints to show some functionalities.

NB: I’m definitely not a guru of any of those technologies.

Install Node

Let’s first install NodeJS on our machine.

  1. Download the latest version. At the time of writing, this version is v16.1.0:

    curl https://nodejs.org/dist/v16.1.0/node-v16.1.0-linux-x64.tar.xz
    
  2. You can install node globally (for exemple in /usr/local) or locally. I do prefer to install stuff locally even on my local machine so that I’m pretty sure there will be no interference with the system nor mistyping while being root which may lead to some catastrophe! For local installations, I have a $HOME/usr directory.

    cd ~/usr
    tar xvf ~/Downloads/node-v16.1.0-linux-x64.tar.xz
    ln -s node-v16.1.0-linux-x64/ node
    

    This will install the node command which is the JavaScript interpreter based on the V8 google JavaScript engine and npm, the NodeJS eco-system package manager.

  3. Add $HOME/usr/node/bin the PATH somewhere in your .bashrc:

    # Nodejs
    export PATH=$HOME/usr/node/bin:$PATH
    
  4. Test it by typing node and npm in a terminal

So far so good!

Create a minimal NodeJS applications

We will create a hello world service that will reply on the following endpoints:

  • / will reply hello to show how to manage the default endpoint
  • /hello will reply hello worldto show how to manage static content
  • /hello/<name> will reply hello <name> to show how to manage dynamic content

Initialise the application

We’ll create an app called hello so first create the working directory and initialize the app hello:

mkdir helllo
cd hello
npm init

npm init will ask some questions (and will propose the default values) about the package name, the entry point, the licence, version, etc.. Keeping the defaults is fine for the moment. This operation will create the package.json file which describes the application.

Notice the scripts section which here has one entry, test which should define how to start the test process and by default there is no testing method that is defined. Scripts in this section are started with npm test or npm what_ever_defined.

The content of package.json is:

{
  "name": "hello",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

Install the dependencies

npm is the package manager for NodeJS applications. It can initialise applications, upload your packages on the global NPM repository and manage application’s dependencies. See the output of npm -l to see the list of all possible actions. Dependencies are packages that bring additional functionalities to the application. We’ll install 2 dependencies:

  • express is the core web server
  • ejs is the template engine to be used with express

To add some packages, simply list them on npm install’s command line:

npm install -y express ejs

The -y options answers yes to any question npm may ask during the installation process.

If we look at the content of package.json, we’ll see there is a new section:

"dependencies": {
  "ejs": "^3.1.6",
  "express": "^4.17.2"
}

One can specify the version of the packages to be installed, if there are to be installed locally for the project only or globally for the system; here we have installed explress’s and ejs’s latest versions locally. npm also takes care to import all packages needed by the packages we are installing. Local packages are stored in the node_modules directory where we will find express and ejs sub-directories along with 63 others which contain packages necessary for express and ejs. All together, this makes 3.7MB for a hello world web application! Yet remember it comes with everything included: web server, interpreter, template engine, etc.

Create the application

As defined in package.json, the file index.js is the entry point to our application. Let’s define it:

// Libraries and global variable section
var express = require('express')
var app = express()

// Server start section. The server will be listerning on port 3000
var port = 3000
var server = app.listen(port, function () {
    console.log(`Server listening at http://localhost:%s`, port)
})

// Routes section
app.get('/', function (req, res) {
    res.send(`hello`)
})

app.get('/hello', function (req, res) {
    res.send(`hello world`)
})

app.get('/hello/:who', function (req, res) {
    res.send(`hello ${req.params.who}`)
})

We can now test the application. Start the server by running node index.js in a terminal and point a navigator to the different endpoints or use curl:

curl http://localhost:3000/
  hello
curl http://localhost:3000/hello
  hello world
curl http://localhost:3000/hello/maurycy
  hello maurycy

And that’s it!

Application’s automatic restart

During the development process, we’ll modify index.js and all other source files constantly. It will be quite painful to stop ans start the application every time… To ease this process we’ll install the nodemon package. nodemon actually supervises the file system and upon of any change in the source code, it will restart the application automatically.

To install nodemon:

npm install nodemon --save-dev # use only for development

As nodemon is only used during development, we give the --save-dev option to npm install. This option adds packages in devDependencies section in package.json which list packages to be used during development phase only.

To start the application with nodemon, type:

PORT=2000 IP=192.168.1.101 \ # This space is important
node node_modules/nodemon/bin/nodemon.js index.js

OK, this is typed only once, but it’s quite painful also. The clean way is to add the line "start": "nodemon index.js" to the scripts section in package.json file and start the application with npm start.

Try to change something in the source code to see the application restarting.

Serving static files

Serving static files is the basis of HTTP service. This concerns .html files, but also styles and images. Express handles easily static files: the only thing is to tell it where the static files ate located.

Where to store the files

First, create the public directory (mkdir public) and tell express that static files will be located there by adding the following line to index.js:

app.use(express.static('public'));

Put two basic HTML files index.html and test.html in this directory:

cat > public/index.html << EOF
<html>
  <body>
    <h1>This is <b>index.html</b> file</h1>
  </body>
</html>
EOF

cat > public/test.html << EOF
<html>
  <body>
    <h1>This is <b>test.html</b> file</h1>
  </body>
</html>
EOF

That’s all. You can try the URLs http://localhost:3000/test.html.

Styles and images

Static stiles and images files can be stored in public/css and public/imahes directories. Put in there an image, say, info.png and a style, say style.css and change test.html to

<html>
  <head>
    <link rel="stylesheet" href="/css/style.css" media="screen">
  </head>
  <body>
    <h1><img src="/images/info.png">This is <b>test.html</b> file</h1>
  </body>
</html>

Simple!

Order for searching the resources

Depending on the position of app.use(express.static('public')) and of the route definition, the behavior of the application will be different.

Let’s take as an example the default endpoint, that is / supposed to render the file public/index.html. If app.use(express.static('public')); is declared before the route app.get('/', function (req, res) {...}, then what will be rendered is the file public/index.html, as expected. However, if the order is reversed, then it’s the content returned by the route that will be rendered.

I believe that the good habit is to have app.use(express.static('public')) before any route definition.

MIME types

The nice thing is that express.static takes care of the mime type based on the extension of the file that is being requested. For example, create a .css file in public (touch public/style.css), get it with curl -v http://localhost/style.css, and observe the Content-Type HTTP header: it should be set to text/css.

Add support for templating

Basics

Templating is provided by the ejs package that we have already installed. Notice that there are many more template engines as for example handlebars, pug, and others.

To use ejs we need to tell express that the template engine is ejs. To do so, add the following line just after var app = express() in index.js:

var app = express()
// set the view engine to ejs
app.set('view engine', 'ejs');

// rest of index.js

ejs assumes template files are located in the views directory and have by default the .ejs extension. For example, to render the documentthat provides help for our application, we will call res.render('help'); which supposes the existence of the file views/help.ejs. So, let’s create the directory views and add the help.ejs file:

mkdir views
cat > views/help.ejs << EOF
This is the help for the application
EOF

Then add the route and call the render() function in index.js:

// Gets the help page
app.get('/gethelp', function (req, res) {
  res.render('help');
})

Notice that the route is independant of the name of the file to be rendered..

To test, try : curl http://localhost:3000/gethelp

Tags

This a a copy and paste from the EJS GitHub page

  • <%: ‘Scriptlet’ tag, for control-flow, no output
  • <%_: ‘Whitespace Slurping’ Scriptlet tag, strips all whitespace before it
  • <%=: Outputs the value into the template (escaped)
  • <%-: Outputs the unescaped value into the template
  • <%#: Comment tag, no execution, no output
  • <%%: Outputs a literal ‘<%’
  • %%>: Outputs a literal ‘%>’
  • %>: Plain ending tag
  • -%>: Trim-mode (‘newline slurp’) tag, trims following newline
  • _%>: ‘Whitespace Slurping’ ending tag, removes all whitespace after it

See here for details about the syntax.

Parameters to templates

ejs is a templating engine, so we should be able to pass some arguments to it. This is done by passing a second argument to render() which is a JavaScript object. For example, change the route /hello/:who by:

app.get('/hello/:who', function (req, res) {
  res.render('hello', {who: req.params.who});
})

Then create the file hello.ejs in views directory:

EJS rendered hello with parameter "<%= who%>"

and try it with http://localhost:3000/hello/Polo.

Partials

ejs is able to include other .ejs files, often called partials. This is typically useful if you want a consistent styling across the site, a constant menu bar, a copyright message, etc. without the hassle to repeat over and over the same code over all the .ejs files. For this, we can define a header file, viewsheader.ejs that will define information, as the style:

<html>
  <head>
    <link rel="stylesheet" href="/css/style.css" media="screen">
  </head>
  <body>

and a views/footer.ejs file which will display the copyright and close a open HTML tags:

  <hr>
  <center>Copyright me</center>
  </body>
</html>

Now, every views/xxx.ejs page will need to have the following format to include the partials:

<%- include('header') %>
  <h1>This is <b>xxx.ejs</b> file</h1>
<%- include('footer') %>

Notice that, just as normal templates, partials can receive parameters in JavaScript object format.

Serving JSON content

By default send() or render() set the Content-Type header to text/html (check this with curl -v ...). If we want to serve JSON content, for example an object returned by an API, we will use the res.json() function. As an example, upon hit on /headers endpoint, let’s return the HTTP headers of the request. Add the following route:

// other routes
app.get('/headers', function (req, res) {
   res.json(req.headers)
})

and test it with curl -v http://localhost:3000/headers

Getting parameters from the environment

There are situations where we want to start the server with some parameters that we can’t or that we don’t want to hard code or store in a file as for example database credentials or network port and IP address the server will listen on. The easy way is to read such parameters from the environment variables.

To do so, change the server startup section in index.js with:

// Get the IP address and the port to listen on from the environment
const port = process.env.PORT || null
const ip = process.env.IP || null
if(ip == null || port == null) {
    console.error("Environment varables 'IP' or 'PORT' not set");
    console.error("Bailing out")
    process.exit(1);
}
// Start listening
var server = app.listen(port, ip, function () {
    var h = server.address().address
    var p = server.address().port
    console.info(`Server listening at http://${h}:${p}`)
})

To start the server we now need to provide both environment variables:

IP=127.0.0.1 PORT=2000 npm start

Source files

At this point, after some final beautifyings, we should have the following files:

  • index.js

     // Libraries and global variable section
     var express = require('express')
     var app = express()
       
     app.use(express.static('public'))
     app.set('view engine', 'ejs')
       
     // Get the IP address and the port to listen on from the environment
     const port = process.env.PORT || null
     const ip = process.env.IP || null
     if(ip == null || port == null) {
         console.error("Environment varables 'IP' or 'PORT' not set");
         console.error("Bailing out")
         process.exit(1);
     }
     // Start listening
     var server = app.listen(port, ip, function () {
         var h = server.address().address
         var p = server.address().port
         console.info(`Server listening at http://${h}:${p}`)
     })
       
     // Routes section
     app.get('/', function (req, res) {
         res.send(`Hello`)
     })
       
     app.get('/hello', function (req, res) {
         res.send(`hello world`)
     // res.json({"sid": sid, "resp": "Hello world"})
     })
       
     app.get('/hello/:who', function (req, res) {
         res.render('hello', {who: req.params.who});
     })
        
     app.get('/gethelp', function (req, res) {
       res.render('help');
     })
       
     app.get('/headers', function (req, res) {
        res.json(req.headers)
     })
       
     app.get('/test', function (req, res) {
         res.render('test');
     })
    
  • public/index.html

     <html>
       <body>
         <h1>This is <b>index.html</b> file</h1>
       </body>
     </html>
    
  • public/test.html

     <html>
       <head>
         <link rel="stylesheet" href="/css/style.css" media="screen">
       </head>
       <body>
         <h1><img src="/images/info.png">This is <b>test.html</b> file</h1>
       </body>
     </html>
    
  • views/hello.ejs

     EJS rendered hello with parameter "<%= who%>"
    
  • views/header.ejs

     <html>
       <head>
         <link rel="stylesheet" href="/css/style.css" media="screen">
       </head>
       <body>
    
  • views/footer.ejs

         <hr>
         <center>Copyright: me</center>
       </body>
     </html>
    
  • views/test.ejs

     <%- include('header') %>
       <h1><img src="/images/info.png">This is <b>test.ejs</b> file</h1>
     <%- include('footer') %>
    

Conclusion

There are many, many more things that could be added about Express and EJS: please refer to the references. Sure, you have not become a ninja on Express nor EJS, but I believe it’s enough to start and just enough to be able to speak about.

References