NodeJS

Introduction to Express

NodeJS Course

Introduction

In the previous section, you got up and running with Node. You learned how to read and write data from files on the server, allowing you to serve up a multi-page website. You may have found this process somewhat verbose and cumbersome, and may have even wondered how on Earth we would manage more complex use cases.

In this course, we will be using a backend framework called Express, which will handle many of the implementation details for us. Express itself is an intentionally barebones and unopinionated framework; it allows us to do many things how we want, and to extend it with only the features we need. However, while this gives us great flexibility in how we do things, it can be a little tricky deciding between multiple viable options at times.

Going forward, we will be diving into how we can use Express in various ways, such as to create a full-stack application using the Model View Controller (MVC) pattern, as well as to create a REST API just like ones you will have used before for things such as the Weather App or the React Shopping Cart projects. There is a lot to take in, so take it steady, and do not be afraid to ask our community for help in the TOP Discord server!

Lesson overview

This section contains a general overview of topics that you will learn in this lesson.

  • Set up a basic Express server.
  • Describe how an incoming request is processed and ultimately responded to by the server.
  • Describe what a middleware function is.
  • Describe how to automatically restart your application when a change is made.

Setting up Express

Let’s make a basic Express app. Inside a new directory, start by running npm init -y to create a package.json. Once that’s created, we can install the Express dependency.

npm install express

We can now create an app.js file that will serve as the starting point for our Express server. We could name this file anything we want, such as index.js or main.js, but we will use the same name used in the Express documentation. Inside app.js, we will have the following:

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

app.get("/", (req, res) => res.send("Hello, world!"));

const PORT = 3000;
app.listen(PORT, () => console.log(`My first Express app - listening on port ${PORT}!`));

Let’s break this down. We import express then call it to initialize the app variable. This is our server.

We then have a route - the line beginning with app.get. We will come back to this in a moment.

Finally, we tell our server to listen for incoming requests on whatever port we specify, via localhost (which is basically just the computer’s local connection). While port 3000 is the default choice, you can use any unused port (for example, Vite’s dev server uses port 5173 by default). Back in your terminal, if you run node app.js then all being well, you should see My first Express app - listening on port 3000! logged.

Congratulations! Your first Express server is now running.

The port variable

For demonstration purposes, we hardcoded a fixed port number above. Usually, the port number would come from an environment variable with a fallback value in case the environment variable does not exist.

const PORT = process.env.PORT || 3000;

If the specified port is already in use, we can change the environment variable value without editing the source code. Also, some hosting services configure their own ports which may differ from a fix value hardcoded in.

A request’s journey

Now that our server is up and running on port 3000, let’s send it a request! In a browser, navigate to http://localhost:3000/ (don’t worry if you forget the slash / at the end; the browser will silently add it for you if so). This action tells the browser to send a GET request to the / path of whatever server is listening at port 3000 on our localhost (which is our Express server!) and display in the window whatever it receives in response.

Whenever you navigate to any web URL this way, this is essentially what you are doing. Navigating to https://theodinproject.com/paths via the address bar is just telling the browser to send a GET request to the /paths path at https://theodinproject.com, then display what it receives in response.

Once you navigate to http://localhost:3000/, you should see Hello, world! appear in the window. Magic, right?

When our server receives our GET request, Express stores the request in a request object. This request gets passed through a chain of functions we call middleware functions until eventually, a middleware function tells Express to respond to the request.

In our example, the request comes through as a GET request to the / path. This matches the route we have in our app.js file.

app.get("/", (req, res) => res.send("Hello, world!"));

We will discuss routes in more detail in a later lesson, but to summarize the above line, it tells Express: “if a GET request comes through to the / path, pass the request through the following chain of middleware functions”. Here, we only have a single function.

If we had defined multiple routes, Express would pass the request through the first route that matched the requested HTTP verb (e.g. GET) and path (/). The order of the routes matters!

Express takes the callback function we gave it and passes the request object into the first parameter (conventionally named req), and a response object into the second parameter (res). Our callback tells the response object to respond to the request by .sending the string "Hello, world!".

There is no more code to run and the function returns. Since Express has been told to respond to the request, it ends the request-response cycle. Meanwhile, the browser receives our server’s response and displays it on screen, which is our "Hello, world!" string. We could send nearly anything in our response. We could even tell Express to send a file.

Auto-restarting your server upon file changes

When you run your server with node app.js, any changes to any JavaScript and JSON files in your project directory won’t be reflected automatically unless you manually interrupt and rerun node app.js. To avoid this manual process, you can use Node’s watch mode by adding the --watch flag, e.g. node --watch app.js. Node will watch app.js for changes, as well as any of the files it ultimately depends on. When it detects a change, it will automatically restart the server just like with Webpack and Vite’s dev servers.

You may also come across Nodemon, a highly configurable package that can also watch for changes and restart your server for you. Node didn’t always have a stable built-in watch mode, so you’re likely to see Nodemon around the place. Our recommendation would be to stick with Node’s built in watch mode via the --watch flag, as this would be by far the simplest method.

Assignment

  1. Spend a few minutes exploring the Express documentation to get a feel for things. We will be referencing a lot of content from the docs in the coming lessons.
  2. Go back to your Basic Informational Site project, install Express and rewrite the project using it! You should be able to do most of this with just a few app.get()s.

Knowledge check

The following questions are an opportunity to reflect on key topics in this lesson. If you can’t answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge.

Additional resources

This section contains helpful links to related content. It isn’t required, so consider it supplemental.

  • It looks like this lesson doesn’t have any additional resources yet. Help us expand this section by contributing to our curriculum.

Support us!

The Odin Project is funded by the community. Join us in empowering learners around the globe by supporting The Odin Project!