Updated:

✒ Reference:Express crash course by Traversy Media
Express API Reference

What is Express?

A fast, unopinionated, and minimalist back-end framework for Node.js. It can be combined with client-side frameworks like React, Angular & Vue to build full stack applications.

You can build an API with Express so that it takes request from the front-end and serves back data in JSON format.

  • Unopinionated: not a high level framework, does not require any fixed design, you have full control of handling the requrest to the server.

Why use Express?

  • Light, fast, free, and popular
  • Makes building web apps with Node.js much easier: full control of request and response
  • Used for both APIs with JSON data and back-end applications
  • It’s all JavaScript; great to use with client side frameworks

Basic server syntax

const express = require('express');

// Init express
const app = express();

// Create your endpoints/route handlers
app.get('/', function(req, res) {
    res.send('Hello World!');
})

// Listen on a port
app.listen(5000);

Basic route handling

What can you do within your route?

  • Fetch data from a database (MongoDB, MySQL, etc)
  • Load pages
  • Return JSON data
  • Full access to request and response
    • req object represents the HTTP request properties for things like URL parameters, query strings, any data sent within the body, HTTP headers, etc.
    • res object represents the HTTP response, and you can use this object to send back JSON data, return template, redirect, etc.
  • Parse any incoming data with the Body Parser
  • Routs can be stored in separate files and exported, since Express has a router.

Using nodemon

Once the following code is added to package.json, you can run the file in developer mode via npm run dev. (npm run start is used for deployment)

  "scripts": {
    "start": "node index",
    "dev": "nodemon index"
  }

Creating a static server with Express

Postman is used: an HTTP client to make requests to the server (get, put, post, delete, request, etc.)

const express = require('express');
const app = express();

// Look for the environment variable, and set it to PORT
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on ${PORT}`));

localhost:5000 will show Cannot GET /, which means that it cannot find a route handler for / (index page).

const express = require('express');
const app = express();

// Create a route to go to a web page
app.get('/', (req, res) => {
    res.send('<h1>Hello World!</h1>');
});

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on ${PORT}`));

Instead of adding a single h1, you can add a whole HTML file.

// Create a route to go to a web page
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

However, this is not ideal since you have to put a route manually for every single page. Express has functionality to make a certain folder as the static folder.

// Create a route to go to a web page by setting a static folder
// .use() is a method used to include a middleware.
app.use(express.static(path.join(__dirname, 'public')));

Any file within the public folder can be open without manually adding the route. i.e. localhost:5000/about.html will show about.html saved within the public folder. Instead of handling the content types and paths, you can just put the files in the public folder!

However, creating a static server that creates regular html files is not what you will usually use Express for. You will mostly create JSON APIs to connect from a front-end or render templates where you can insert dynamic data.

Middleware functions

A middleware is a stack of functions that executes whenever a request is made to a server. These functions have access to the request and response objects. Express has its own middleware, just like how 3rd party packages do. It takes three arguments: req, res, and next. next() calls the next function in the stack.

Middlewares can:

  • Execute any code
  • Make changes to the request/response objects
  • End response cycle
  • Call next middleware in the stack (with the next function)
const moment = require("moment");

const app = express();

// Create a middleware that prints the url
const logger = (req, res, next) => {
  console.log(
    `${req.protocol}://${req.get("host")}${
      req.originalUrl
    }: ${moment().format()}`
  );
  next();
};

Everytime a request is made, the logger is executed. If a GET request is made (i.e. GET http://localhost:5000/api/members), it will print something like http://localhost:5000/api/members: 2021-12-15T19:40:33+09:00 to the console.

GET request

Creating a JSON API

Data used:

// Members.js const members = [
    {
        id: 1,
        name: 'John Doe',
        email: 'john@gmail.com',
        status: 'active'
    },
    {
        id: 2,
        name: 'Bob Williams',
        email: 'bob@gmail.com',
        status: 'inactive'
    },
    {
        id: 3,
        name: 'Shannon Jackson',
        email: 'shannon@gmail.com',
        status: 'active'
    },
];

module.exports = members;

Getting the members list

The code below gets all members, by returning the members as a JSON file. res.json(members) will return a JSON file without using JSON.stringify. You can send a GET request on http://localhost:5000/api/members on Postman, and retrieve the members list in a JSON file.

// index.js
const express = require('express');
const path = require('path');
const members = require('./members');

// Gets all members
app.get('/api/members', (req, res) => res.json(members));

// Set static folder
app.use(express.static(path.join(__dirname, 'public')));

// Look for the environment variable, and set it to PORT
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on ${PORT}`));

If you send a GET request on http://localhost:5000/api/members using nodemon, you can retrieve the members in a JSON file.

// Get single member
app.get("/api/members/:id", (req, res) => {
  // :id represents an input of any id.
  // req.params.id returns the id of the request as a string.
  const found = members.some(member => member.id === parseInt(req.params.id));

  if (found) {
    // If the id exists, return the JSON representation of the member.
    res.json(members.filter(member => member.id === parseInt(req.params.id)));
  } else {
    // If it doesn't, change the status and send a message
    res.status(400).json({ msg: `No member with the id of ${req.params.id}` })
  }
})

Using a router

You can separate the router to a different file.

// index.js
const express = require("express");
const path = require("path");

const app = express();

// Set static folder
app.use(express.static(path.join(__dirname, "public")));

// Members API Routes
// first param: route, second param: file
app.use('/api/members', require('./routes/api/members'));

// Look for the environment variable, and set it to PORT
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on ${PORT}`));

Getting a single member

// members.js in routes/api
const express = require('express');
const router = express.Router();
const members = require("../../members");

// Gets all members
router.get("/", (req, res) => res.json(members));

// Get single member
router.get("/:id", (req, res) => {
    const found = members.some(member => member.id === parseInt(req.params.id));
  
    if (found) {
      res.json(members.filter(member => member.id === parseInt(req.params.id)));
    } else {
      res.status(400).json({ msg: `No member with the id of ${req.params.id}` })
    }
  })

module.exports = router;

POST request

Using a body parser

// members.js

// Send the given data to response
router.post('/', (req, res) => {
    res.send(req.body);
});

The code above sends the body of the request to the response.

Sending a POST request using postman: header body

You need to use a body parser to parse the data sent into the body. You can initialize the parser as a middleware.

// index.js

// Body Parser Middleware
app.use(express.json()); // handle raw json
app.use(express.urlencoded({ extended: false })); // handle form submissions

Adding a member

// Create member
router.post('/', (req, res) => {
  // Add a new member
  const newMember = {
    id: uuid.v4(),
    name: req.body.name,
    email: req.body.email,
    status: 'active'
  }

  // Bad request if name or email nonexisting
  if (!newMember.name || !newMember.email) {
    return res.status(400).json( { msg: 'Please include a name and email' });
  } 

  // Add the new member to the array
  members.push(newMember);

  // Return the entire array
  res.json(members);
});

When the email is not provided: When both name and email are provided:

When dealing with new API, you deal with real database instead of a file with your data. In that case, you install a package to connect to the database (i.e. Mongoose for MongoDB). Rather than pushing the new member to the array, you use a method such as members.save(newMember).

PUT request

Updating a member

// Update member

router.put("/:id", (req, res) => {
  // Check to see if the member exists.
  const found = members.some(member => member.id === parseInt(req.params.id));

  if (found) {
    // If the id exists, 
    const updMember = req.body;
    members.forEach(member => {
      // If the member has the same id as the given id, then change the updated parameter.
      if (member.id === parseInt(req.params.id)) {
        member.name = updMember.name ? updMember.name : member.name;
        member.email = updMember.email ? updMember.email : member.email;

        res.json( {msg: 'Member updated', member });
      }
    });

  } else {
    // If it doesn't, change the status and send a message
    res.status(400).json({ msg: `No member with the id of ${req.params.id}` })
  }
})

Sending a PUT request using postman: updated

DELETE request

// Delete member

router.delete("/:id", (req, res) => {
  const found = members.some((member) => member.id === parseInt(req.params.id));

  if (found) {
    // If the id exists, filter out the id and 
    // return all the members except the one that was deleted
    res.json({
      msg: "Member deleted",
      members: members.filter(
        (member) => member.id !== parseInt(req.params.id)
      ),
    });
  } else {
    // If it doesn't, change the status and send a message
    res.status(400).json({ msg: `No member with the id of ${req.params.id}` });
  }
});

Sending a DELETE request using postman: deleted

Server-rendered templates

You usually build (1) an API to connect to the frontend or (2) a complete server-side app where you use templates. The following parts explain the latter.

You can use the handlebars module (installed by npm install express-handlebars) and the Bootstrap template (the CSS link inserted in main.handlebars).

Rendering the members

First, initiate the handlebars middleware.

// In index.js
var express = require('express');
var exphbs  = require('express-handlebars');

var app = express();

app.engine('handlebars', exphbs());
app.set('view engine', 'handlebars');

app.get('/', function (req, res) {
    res.render('index', {
    res.render('index', {
      title: 'Member App',
      members
    });
});

app.listen(3000);

Create main.handlebars file in views/layouts folder.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- CSS only -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" 
    integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <title>Members App</title>
</head>
<body>
    <!-- Triple curly braces around 'body' 
    wherever you want to output the rest of your views with handlebars-->
    <!-- mt-4 is a bootstrap class -->
    <div class="container mt-4">
        }
    </div>
</body>
</html>

Create index.handlebars file in views folder.

<!-- To use variables in handlebars, use double curly brackets -->

<h1 class="text-center mb-3"></h1>
<h4>Members</h4>
<ul class="list-group">
    
        <li class="list-group-item">: </li>
    
</ul>

Now the members are loaded at localhost:3000.

membersloaded

Creating a form

You can make a request to API from the form.

<!-- index.handlebars -->

<h1 class="text-center mb-3"></h1>

<!-- You can send the JSON from a form instead of Postman. -->
<form action="/api/members" method="POST" class="mb4">
    <div class="form-group">
        <label for="name">Name</label>
        <input type="text" name="name" class="form-control">
    </div>
    <div class="form-group">
        <label for="email">Email</label>
        <input type="email" name="email" class="form-control">
    </div>
    <input type="submit" value="Add Member" class="btn btn-primary btn-block">
</form>

<h4>Members</h4>
<ul class="list-group">
    
        <li class="list-group-item">: </li>
    
</ul>

Now you have a form on your page.

formapp

Once a form is submitted, the relevant data will be sent to routes/api/members.js to hit router.post('/', (req, res) => {...}. However, if you are using server rendered views, you will redirect back to the index page instead of returning a JSON.

  // Static folder: Return the entire array
  res.json(members);

  // Server-rendered view: 
  res.redirect('/');

You can also use Passport.js as your template engine. (passport-local for server-side app)

Leave a comment