knowledge-kitchen

Express.js - Intro (in Javascript)

Throw together a quick Javascript-based web server.

  1. Overview
  2. Routing Requests
  3. Middleware
  4. Debugging Express
  5. Handling HTTP POST Requests
  6. Handling File Uploads
  7. Proxying Requests
  8. Environmental Variables
  9. Parameters
  10. Template Engines
  11. Authentication
  12. Database Integration
  13. Conclusions

Overview

Concept

Express.js is a web server framework that runs in the Node.js Javascript environment.

HTTP request and response

Setting up an NPM project

Using express assumes you have initialized a Node project with the Node Package Manager (NPM).

cd back-end # or whichever directory you want to house the web server code
npm init # enter `server.js` as the entry point, when asked.
npm install express --save

Set up a placeholder Express app

Place the following into a file named app.js - this creates an express web server app that serves no purpose… for now.

// import and instantiate express
const express = require("express") // CommonJS import style!
const app = express() // instantiate an Express object

// we will put some server logic here later...

// export the express app we created to make it available to other modules
module.exports = app

Create code to launch the express app

Put the following into a file named server.js, and enure the package.json settings file mentions this as the project’s main entry point.

#!/usr/bin/env node

const server = require("./app") // load up the web server

const port = 3000 // the port to listen to for incoming requests

// call express's listen function to start listening to the port
const listener = server.listen(port, function () {
  console.log(`Server running on port: ${port}`)
})

// a function to stop listening to the port
const close = () => {
  listener.close()
}

module.exports = {
  close: close,
}

Launch it!

You are now ready to run the express app.

npm install nodemon --save-dev

What now?

You now presumably have an Express server up-and-running on your local machine at port 3000. What to do with it?

Follow along

The Express.js code examples in this slide deck are available on GitHub - the code is already set up and ready to run.

Routing Requests

Concept

Routes instruct express what to do in response to incoming HTTP(S) requests.

Example 1

A route that listens for any HTTP GET request for the / path, and responds with the plain text, ‘Hello!’.

app.get("/", (req, res) => {
  res.send("Hello!")
})

Example 2

A route that listens for any HTTP GET request for the /html path, and responds with a static HTML file.

// respond to any GET requests for /html-example with an HTML document named some-page.html
app.get("/html-example", (req, res) => {
  res.sendFile("/public/some-page.html", { root: __dirname })
})

Example 3

Inform express that the public directory contains all static files that it should just blindly serve up when requested under the /static route.

// tell express that any requests for '/static' will map out to the 'public' directory, where we place static content
app.use("/static", express.static("public"))

Example 4

JSON is a common format for sending data between client and server in apps.

// route for HTTP GET requests to /json-example
app.get("/json-example", (req, res) => {
  // assemble an object containing the data we want to send
  const body = {
    title: "Hello!",
    heading: "Hello!",
    message: "Welcome to this JSON document, served up by Express",
    imagePath: "/static/images/donkey.jpg",
  }

  // send the response as JSON text to the client
  res.json(body)
})

Middleware

Concept

As you have seen, when a request is made, a route function executes that determines how to respond.

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

Setting up a middleware function

We call express’s use function and pass it our middleware function as an argument.

app.use((req, res, next) => {
  //...
})

Example 1

Imagine two middleware functions …

First:

// custom middleware - first
app.use((req, res, next) => {
  // make a modification to either the req or res objects
  res.addedStuff = "First middleware function run!"
  // run the next middleware function, if any
  next()
})

Second:

// custom middleware - second
app.use((req, res, next) => {
  // make a modification to either the req or res objects
  res.addedStuff += " Second middleware function run!"
  // run the next middleware function, if any
  next()
})

Example 1 (continued)

… followed by a route:

// route for HTTP GET requests to /middleware-example
app.get("/middleware-example", (req, res) => {
  // grab data passed along by the middleware, if available
  const message = res.addedStuff
    ? res.addedStuff
    : "Sorry, the middleware did not work!"
  // use the data added by the middleware in some way
  res.send(message)
})

Debugging

Concept

As you work on your express-enabled web servers, you will inevitably want to be aware of a few useful debugging tools.

Morgan

Morgan will log a bit of info about each incoming request to the server’s command shell output.

npm install morgan --save
const morgan = require("morgan") // middleware for nice logging of incoming HTTP requests
app.use(morgan("dev")) // dev style gives a concise color-coded style of log output

Postman

Postman is a desktop app that allows you to trigger custom HTTP requests to your local development web server.

Mocha and chai

Mocha and chai are two 3rd-party Node.js modules often used together to perform unit testing of code.

ngrok

ngrok is a tool that will provide a publically-accessible web address that forwards requests to a server you run on your local machine.

Handling HTTP POST Requests

Concept

Many web sites and apps allow users to submit data to a server via forms. Usually these requests are submitted via HTTP POST, with data in the body (i.e. payload) of the request.

app.use(express.json()) // decode JSON-formatted incoming POST data
app.use(express.urlencoded({ extended: true })) // decode url-encoded incoming POST data

Example

Imagine an HTML form that sends data to the server via HTTP POST…

<form action="/post-example" method="POST">
  <input type="text" name="your_name" placeholder="Your name" /> <br />
  <input type="text" name="your_email" placeholder="Your email" /> <br />
  <input type="checkbox" name="agree" /><label
    >I agree to your onerous conditions</label
  >
  <br />
  <input type="submit" value="Submit!!!" />
</form>

… and an Express route receives that data after the middleware has kindly added it to the req object’s body property.

app.post("/post-example", (req, res) => {
  const name = req.body.your_name
  const email = req.body.your_email
  const agree = req.body.agree
  // now do something amazing with this data...
  // ... then send a response of some kind
})

Handling File Uploads

Concept

Clients can upload files to the server as part of their HTTP POST requests.

npm install multer --save
const multer = require('multer')

Setup

multer can save uploaded files to a number of different destinations. Here, we tell multer to save uploaded files into a directory named public/uploads, with a filename based on the current time.

// enable file uploads saved to disk in a directory named 'public/uploads'
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, "public/uploads")
  },
  filename: function (req, file, cb) {
    cb(
      null,
      `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`
    )
  },
})

Then instantiate a multer object.

const upload = multer({ storage: storage })

Client-side code

Take the following HTML form that allows users to upload files to the server as part of the POST request.

<form action="/upload-example" method="POST" enctype="multipart/form-data">
  <input name="my_files" type="file" multiple />
  <input type="submit" value="Submit!!!" />
</form>

Server-side code

multer middleware will automatically save any uploaded files in the request into the specified directory, rename them as instructed, and make a field named req.files containing the paths to the files on the server.

// route for HTTP POST requests for /upload-example
app.post("/upload-example", upload.array("my_files", 3), (req, res, next) => {
  // check whether anything was uploaded
  if (req.files) {
    // success! send data back to the client, e.g. some JSON data
    const data = {
      status: "all good",
      message: "yup, the files were uploaded!!!",
      files: req.files,
    }
    res.json(data) // send respose
  }
})

Proxying Requests

Concept

We often use our web server as a sort of proxy, relaying requests from the client to some other service, such as an external API or database, and relaying the responses from these other services back to the client.

Example

Let’s see how to have an express route proxy a request from the client to an API and back, with no modification of the data.

npm install axios --save
// proxy requests to/from an API
app.get("/proxy-example", (req, res, next) => {
  // use axios to make a request to an API for animal data
  axios
    .get("https://my.api.mockaroo.com/users.json?key=d9ddfc40")
    .then(apiResponse => res.json(apiResponse.data)) // pass data along directly to client
    .catch(err => next(err)) // pass any errors to express
})

Environmental Variables

Concept

Hard-coding values like credentials for APIs and databases directly into production code is considered bad practice.

Setup

A 3rd-party middleware named, dotenv, makes it easy to store such values as environmental variables that are then imported into the app.

npm install dotenv --save
require("dotenv").config({ silent: true })

Usage

Let’s place the credentials for the API we used earlier into a file named .env:

API_BASE_URL=https://my.api.mockaroo.com/animals.json
API_SECRET_KEY=d9ddfc40
// same route as above, but using environmental variables for secret credentials
app.get("/dotenv-example", (req, res, next) => {
  // insert the environmental variable into the URL we're requesting
  axios
    .get(`${process.env.API_BASE_URL}?key=${process.env.API_SECRET_KEY}&num=10`)
    .then(apiResponse => res.json(apiResponse.data)) // pass data along directly to client
    .catch(err => next(err)) // pass any errors to express
})

Parameters

Concept

Express routes can have parameters.

Example

The following example route accepts an ID into a parameter, makes a request to an API for a record with that ID, and returns the results to the client.

// this route is very similar to the dotenv-example route, but using async/await syntax for a change
app.get("/parameter-example/:animalId", async (req, res) => {
  // use axios to make a request to an API to fetch a single animal's data
  // we use a Mock API here, but imagine we passed the animalId to a real API and received back data about that animal
  const apiResponse = await axios
    .get(
      `${process.env.API_BASE_URL}?key=${process.env.API_SECRET_KEY}&num=1&id=${req.params.animalId}`
    )
    .catch(err => next(err)) // pass any errors to express

  // express places parameters into the req.params object
  const responseData = {
    status: "wonderful",
    message: `Imagine we got the data from the API for animal #${req.params.animalId}`,
    animalId: req.params.animalId,
    animal: apiResponse.data,
  }

  // send the data in the response
  res.json(responseData)
})

Template Engines

Concept

Sometimes, it is desireable to respond to incoming requests with content that is formatted in a way that is ready to be displayed by a web browser.

Creating a Pug Template

For example let’s install and use the Pug template engine.

npm install pug --save
app.set("view engine", "pug")
html
head
title= title
body
h1= content

Using a Pug Template

To use a Pug template, simply create a route that instructs Express to respond with the contents of the template by using the render function.

app.get("/", function (req, res) {
  res.render("index", {
    title: "My First Templated Site",
    message: "Welcome to your first dynamic templated page!!",
  })
})

Single Page Applications

Template engines are less useful for web apps that are set up as single page applications.

{
    'title': 'My First Templated Site',
    'message': 'Welcome to your first dynamic templated page!!'
}

Front-Ends Built With React.js

React.js-based front-ends are built as single page applications.

Authentication

Concept

Many web apps require users to sign up and log in.

Database Integration

Concept

Most classic technology stacks that rely on Express.js for the back-end use MongoDB as the database.

Conclusions

You now have an baseline understanding of what express.js is, how it works, some useful middlewares, and various route patterns.