Express.js Tutorial: Getting Started with Express

Learn how to use Express.js and its middlewares with this Express.js tutorial

Share This Post

Any modern web application has two main parts: the frontend and the backend. In fact, one without the other is pretty much useless. The frontend is the part running on the user’s device, and it includes the User Interface and some basic functionalities. Yet, if we just rely on the device we cannot get outside information, nor sync data across devices. Fortunately, we have the backend for that. Express.js is a powerful JavaScript tool that allows us to create a backend application, as simply as possible.

In this tutorial, we dive directly into Express.js. Hence, you should be already familiar with JavaScript. If you aren’t, you can just have this quick read on JavaScript getting started, and this other guide on Vue.js and Node.js.

What is Express.js?

Express.js is a JavaScript framework to build a backend web-app. That is, to write some code that does not run on the user’s device. Instead, it runs on a server somewhere over the Internet. The purpose of that is to create something that the frontend (code running on the user’s device) can interact with.

If you have a weather application on your smartphone, it will have to ask to some server what is the weather. As such, there will be a backend application that answers that.

Of course, many technologies to create a backend exist. Even better, they do not depend on the technology that you have on the client-side. JavaScript is just one of the many languages you can use in the backend, and express just one of the many frameworks.

So what makes Express unique? It is very lightweight (i.e. fast to execute) and very minimalistic. It serves just one purpose: to answer HTTP calls. So, it does not give you experience features, such as authentication, logging, authorization, and so on. This may sound like a negative note, but it is actually positive.

In fact, there are many different ways to implement experience features, and no way is the right way. Thus, Express.js gives you just the basics, and if you want you can build only the features that you need, on top of that, and in the way you like.

Express.js is easy to use, and even easier to maintain. This mostly thanks to the architecture based on middlewares. What do we mean by that? Read the next chapter and find that out.

The Middleware in Express.js

Express.js uses the powerful concept of middleware, which allows us to build a modular application.

Remember, the goal of our application is to listen to HTTP requests and provide responses to them. As such, we can consider each interaction to have one request, and one subsequent response. Obviously, each interaction starts with a request and an empty response, and as we process it we build the response.

So, imagine your Express.js app as a set of pipes, chained one after the other. The request is water running inside the pipes. Each pipe, in reality, is a middleware. And, each of them gets a chance to read the request, modify the response, but also read the response coming from the previous middlewares.

Express.js middlewares are like a set of pipes, request go through them and eventually a response comes out
Express.js middleware work pretty much like pipes, they process a request and output a response on the other side.

In this way, you can build all the code to address a request as a set of smaller components (the middlewares), that you will engage in sequence, one after the other. Even if this seems trivial, it is an amazing capability in terms of write clear code.

Create your first Express.js App

Creating a web app with Express.js is simple. We just need to install express, start to write some middlewares, and then link them to HTTP requests.

Installing Express.js

Obviously, we need to start by installing Express.js, which is a Node.js package. Since this code will run on the server, and not on the client, you should create a dedicated empty folder for it. Configure that folder to be a Node.js project with the following command.

npm init

Now, we are ready to install express.

npm install --save express

As simple as that, we now have Express.js installed and we are ready to write some code.

Listening to calls

Our Express.js app will have two main parts. First, we need to define middlewares and associate them with HTTP calls. Then, after all this configuration is ready, we can tell our app to actually listen.

Listening means that the app pops up a web server and be ready to answer HTTP calls. Whenever a call is made, the app that is listening will look through its configuration and send it to the correct middleware.

So, in our index.js (which is our entry point), we can import express.js and make it listen.

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

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

This is a random port, normally we want HTTP to listen on port 80. Now, to run the app we can simply call our index.js file with node.

node index.js

If we run this command, the message will log to the console. However, nothing else will happen. That’s because, even if the app is listening, it has no instruction on how to process requests.

Routes

A route is an association of a middleware (i.e. a JavaScript function) to a specific path.

We can start with a simple example, a keep-alive page. Imagine we want that the page http://localhost:3000/alive returns always OK. Even more broadly, we want that the /alive path returns OK, because the application is not necessarily related to the host that is running it at that specific moment. You may run your app on as many hosts as you want in fact.

We can do that by associating the /alive route with a function that transforms the request so that an “OK” text appears. We can do that with an anonymous function, that takes a request and response as parameters.

app.get('/alive', function(req, res) {
  res.send('OK');
});

As simple as that, whenever we reach our site on the /alive URL, we will get an OK as a response. Our function will indeed send it on the response.

You should define your routes before you call app.listen(). And, as you might have guessed, you have one method for each HTTP method (e.g. app.get(), app.post(), app.put() and so forth).

Route Parameters

Sometimes, you want some parts of your URL to vary. For example, if we are getting a post, depending on the post ID that we provide, we want to obtain a different result (a different post indeed). We can do that with the colon notation, followed by the parameter name.

So, if in our route we use /posts/:id, this will match anything from /posts/1235 to /posts/abcd-fwe012. Basically, :id means “take any URL-safe character and place it into a variable named ID”. How do we access this variable? It is inside req.params.

app.get('/posts/:id', function(req, res) {
  const id = req.params.id;
  res.send(`You requested post ${id}`);
});

You can even have multiple params in your route, for example /posts/:author/:id.

Using Middlewares

This is where express.js shows its full potential. Until now, we saw how to associate one function to a route, but the truth is we can associate multiple functions to each route.

Indeed, we can simply add more functions to a route just one after the other:

const f1 = (req, res, next) => { /* some code */ }
const f2 = (req, res, next) => { /* some code */ }
const f3 = (req, res, next) => { /* some code */ }

app.get('/something', f1, f2, f3);

In the example above, we will engage f1 first, then f2, and finally f3. Note that our functions have one more parameter, next. That is where you receive a reference to the next middleware, the function to call once your function finishes.

For example, imagine you have a middleware that logs the request into the console. After the logging, you want to call next() so that the request can continue to be processed.

function logMiddleware(req, res, next) {
  console.log(`${req.method} ${req.url}`);
  next();
}

Now, you can wire it to your routes like so:

app.get('/alive', logMiddleware, (req, res, next) => {
  res.send('OK');
  next();
});

Why do we call next() even inside the code for our route? We can omit that, but the best practice is to always us next(). Indeed, this allows us to attach some middlewares after the route has been processed, so that for example we can log also the response. Indeed, if we also want to log the status code we should use the logMiddleware last.

Global Middlewares

Sometimes, we want to attach some middleware to all the routes, or to more than a single route. Having to map each middleware individually onto every route is time-consuming and naturally prone to error. Instead, we can use app.use().

With this special function, we attach one or more middleware to all the routes. This is perfect for our logMiddleware.

app.use(logMiddleware);

What is the order of execution? It is simply the order in which middlewares are applied. So, if we use app.use() after app.get() or similar routes, the middlewares attached by use() will be executed after the route. Instead, if we use it before the routes, they will be executed before the route.

We can do even more with app.use(). In fact, we are not constrained to apply middlewares to the entire application. We can apply them only to a part of it, by specifying a path.

app.use('/posts', someMiddleware);
app.use('/authors', someOtherMiddleware');

Now, every route starting with /posts (e.g. /posts/123) will pass through someMiddleware. Instead, every route starting with /authors will pass through someOtherMiddleware.

Nesting Routes

The approach we saw so far can work just fine for smaller applications, but as soon as you start to have many routes it will start to mess things up. You want to have some order in your app, and that’s where nested routes come into play.

The app.use() function is so powerful because we are not limited only to middlewares. Instead, we can provide also an express.Router, which is virtually another instance of express. Completely another app.

First, we need to create our router (we often call it api) and attach some routes to it.

const express = require('express');

const api = express.Router({ mergeParams: true });
api.get('/', (req, res, next) => { /* some code */ });
api.post('/', (req, res, next) => { /* some code */ });
api.get('/:id', (req, res, next) => { /* some code */ });
api.put('/:id', (req, res, next) => { /* some code */ });

Now, we have a router with some routes in it. Note the mergeParam parameter set to true: it will allow us to integrate request parameters when we attach it to the main application as if it was an integral part of it.

Now, we can use our router as simply as that:

const app.use('/posts', api);

A good approach for scalability would be to have each router into a different file, then export it and require it in the main file where you have app.

Even more than that, you can nest a router into another router. However, this inevitably means that your path gets longer and longer. This, as you can imagine, is a bad practice: you want your paths to be as short as possible. Not necessarily short in terms of character (so abbreviations is not the solution), but short in terms of levels (the number of slashes). You shouldn’t go beyond three levels /resource/:id/resource. In addition to that, you may want to add the API version before that, but that is it (/api/v1/resource/:id/resource).

Handling Error

If a middleware throws an error and it’s not handled within that middleware, we have a problem. Instead, if we just want to pass it along for processing we should catch it in the middleware, and pass it to the special error handler middleware.

Unlike normal middlewares, the middleware we use to handle errors expect a fourth parameter: err. You can imagine, that is the error itself. In our other middlewares, whenever we want to engage the error handler we call next() by providing the error. The following example will help you understand.

app.get('/fail', (req, res, next) => {
  try {
    let a = 1 / 0;
  } catch (err) {
    next(err);
  }
});

app.use((err, res, req, next) => {
  console.log(err);
});

We do not need to use any special function, app.use() will work just fine. We only need to specify the err parameter, which is the first parameter.

Enrich your Express.js Experience

What we saw so far gives you all the tools you need to understand how to create a powerful express.js app. Yet, it is true that you will need to look up for additional bits and pieces that we did not cover in this tutorial. In fact, our goal is to help you get started on your own, and also teach you how and how to get the things you need.

Before we go on with some further reading, here are some things you might need a lot.

The first is the JSON parser. If we want to create an API (and that’s what we are doing), and not serve HTML for the user, we want to use JSON. So, we want to parse it to access it in our request. Express.js offers an out-of-the-box middleware to do so: express.json(). When we parse the JSON, it will be stored in the req.body object.

app.use(express.json());

app.post('/resources', (req, res, next) => {
  res.send(req.body);
});

Whenever we send an object in res.send(), Express will automatically JSON-encode it for us. In addition to sending a response, we may want to set a status (different from 200, the default), with res.status().

app.use(express.json());

app.post('/resources', (req, res, next) => {
  res.status(201);
  res.send(req.body);
});

To learn more about Express.js, you can lookup take a look at the official docs.

Express.js for Full Stack Developers

As always, we cannot close a tutorial without giving you something to practice. This will help you fix all those concepts in your mind before moving to the next big thing.

This tutorial is part of the free Full Stack Development course, and as part of it, we are building a pretend bakery store website that you can find on GitHub at alessandromaggio/full-stack-course. So far, we worked only on the frontend, now it is time to move to the backend.

As an exercise, move all the client-side code into a /client folder, then create a new /server folder and add your Express.js app here. To start, it should have a /alive route always returning OK, and a middleware able to log requests with status code (res.statusCode) for all the routes.

Once you finish, you can double-check you did everything correctly by looking at this commit on GitHub.com.

Conclusion & Further Reading

With this short tutorial, we covered all the tools you need to get started with Express.js. Yet, the is more road to cover to become a professional. Here I want to share with you a few resources that you absolutely need to read to master Express.js and writing large apps without shooting yourself in the foot along the way (aka building something that becomes too cumbersome to maintain that you eventually abandon it).

Start with the Bulletproof node.js project architecture. This is THE guide on how to structure your project. In this complete article, Sam Quinn (@SantyPK4) will explain how to scaffold your project, how to arrange your files, and why it is important. Following his suggestions, you will create a project that you are able to maintain for years. Personally, I have been using this approach for a while and it just couldn’t be better. It also comes with an example on GitHub.com, so you can look at all the details there.

The second reading I suggest is Building and using APIs. Read this after the Bulletproof node.js project architecture, which is more important. While the project architecture explains how to structure your files and folder, this guide shows you how to structure your path and interactions that you allow for the user. Express.js is the perfect framework to create APIs, and APIs are what we are creating indeed. Hence, before going wild with paths and resources, read that guide. You can read the same article on GitHub here. Oh, and it’s from The White House.

With all this goodness, you will create awesome applications – both for you, for other developers, and for the users. Keep on developing!

Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.
Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.

Alessandro Maggio

2021-04-29T16:30:13+00:00

Unspecified

Full Stack Development Course

Unspecified