Serving A Web Page Using Node.js and Express

Randolph Perkins
7 min readMar 1, 2021

--

Node.js is an open-source, cross-platform runtime environment that allows developers to generate server-side tools and applications using JavaScript, and to be able to do so outside of a browser context. Using this framework has many advantages: Node.js requires less context-shift between client-side and server-side code, it is optimized for scaleability and for overcoming many frequent web-development problems, and it also has NPM or node package manager which allows access to hundreds of thousands of packages for installing useful dependencies. As a result, it is possible to use Node.js alone as a framework for writing web servers, by implementing Node’s HTTP package with JavaScript. However, other commonly used web development tasks, such as handling specific tasks with HTTP request methods, are not directly supported by Node alone. This is why the most popular web framework to be paired with Node.js is Express, and doing such expands the capabilities for writing web servers in JavaScript tremendously. We will first describe some of the mechanisms which Express provides, and then I would like to exhibit how to serve a simple application using the two frameworks.

Express — Fast, Unopinionated, and Minimalist

As stated above, Express is the most popular web framework for Node, and that is for a myriad of reasons. To begin with, Express possesses the ability to write different handlers for HTTP request methods, and to do so at different URL paths. Developers may also set ports, and utilize templates for rendering HTTP responses. Express also allows for the seamless integration of middleware into the request handling sequence. In fact, it is the utilization of a wide array of middleware packages, written to resolve the most common issues with web development, that contrive to make Express such a powerful tool when used in tandem with Node. There are an inexhaustible number middleware packages available, able to handle everything from cookies and login information to POST data and security headers. The aforementioned descriptors of Express are defined as followed:

  • Opinionated vs Unopinionated: Opinionated web frameworks are ones which are set up to perform optimally in a specific domain, and as such are less flexible for more general tasks. A more opinionated framework may require middleware packages to be installed and utilized in a particular order, for example. Also in such a framework it may be necessary to structure the code base for an application in a specific manner, e.g. all in one file, or in separate dedicated files for carrying out each task. Express has no restrictions in this respect, and is considered an unopinionated framework. Thus the file structure may be more laissez faire, and middleware packages can be deployed as preferred by the developer.
  • Fast and Minimalist: Express can be said to be overlaid in a manner which does not obfuscate the major features of Node. Also, since Express delegates so many tasks to middleware components, many of which were designed explicitly for working in tandem with Express, the result is a development pipeline which is able get web applications up and running in a shorter amount of time than with previous approaches.

Getting Started

To explore how these two valuable frameworks are used in tandem, it would be useful to examine the code necessary to create a simple web application and generate a few HTTP requests via the server. First it is necessary to install Node.JS and NPM for installing packages. For Windows and Mac OS this may be accomplished using the installer on the Node website: https://nodejs.org/en/. After doing so, the current versions can be viewed on the command line by typing node -v for Node, and npm -v for NPM. Next we may install Express in similar fashion by visiting https://expressjs.com/. One interesting feature is the Express generator, which creates an Express application skeleton, replete with an optimal file structure which includes useful folders and routes (https://expressjs.com/en/starter/generator.html). However, this is not necessary, and it may be preferred to just install it via the website and manually create one’s own file structure.

A Basic Express Web Server

Once the two frameworks are installed, we can now create an Express application. First, using Node’s require() method, we can call Express into our program. It is conventional to save a new instance to a variable, often simply called ‘app’. Next we will want to specify a port on which the server will listen for requests. After this, an HTTP request method may be called by the Express instance, which will pass in a callback function that has instructions based on whether or not the request was successfully made. For any request method call implemented by the Express framework, the callback function will always have two parameters: a request object, and a response object. The first object can contain data which may be sent to the specified resource, and the latter that of the returned response from that resource. By convention these two parameter names are ‘req’ and ‘res’, although they may be given any name so long as they conform to JavaScript variable naming conventions. Finally, this file will utilize a .listen() method, in which a message may be written to the console indicating that the server is listening at the specified port. This code is written as follows:

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

app.get('/', (req, res) => {
res.send('Welcome to my new Express application!')
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}!`)
});

This is the simplest Express application possible. If this is saved as app.js, it can be seen in the browser by typing node ./app.js in the command line.

Basic Routing, Examined

In the previous example we made an HTTP ‘GET’ request to the server. Routing is the means by which an application responds to a client request intended for a particular endpoint. This endpoint is either a URI or a path, and is accessed by means of a specific request method, such as ‘GET’, or ‘Post’. Each individual route has at least one handler function, which is executed when the route is matched, and in Express this is delineated by the following structure: app.METHOD(PATH, HANDLER). The app is the instance of Express, the method an HTTP request method, path being the path on the server, and the handler is the function which is executed. Above we wrote a ‘GET’ request to the application’s home page. A few more examples are as follows:

app.post('/', function (req, res) {
res.send('Got a POST request')
})
app.put('/user', function (req, res) {
res.send('Got a PUT request at /user')
})
app.delete('/user', function (req, res) {
res.send('Got a DELETE request at /user')
})

In the above examples, we made requests to the application’s homepage and its ‘/user’ route, respectively, performing different actions based on the method. It is also important to note that route paths, which describe the endpoints to which requests can be made, may be strings as well as regular expressions. For example, a route with a path given as ‘/ab?cd’ will match both acd and abcd.

Express Middleware: Task Delegation

Middleware is used quite extensively in Express applications, for tasks such as serving static files, error handling, and compressing HTTP responses. Usually route functions end up terminating the request/response cycle, typically through returning a response to the client. Middleware functions usually performs a task on the request or response, before calling more middleware immediately following. If a middleware function does not end the cycle, it must call the .next() method to pass control on to the next function. There is an abundance of middleware functions which are actually maintained by the Express team, in addition to the packages available via NPM.

To use a middleware package on an Express application, it needs to first be installed via NPM. Then the package needs to be brought in to a file via .require(), before it may be added to the stack, which is done so by the .use() method. Middleware and routing functions are called in exactly the order they are declared in the file structure. This process is shown in the following example:

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

// An example middleware function
let middleware_function = function(req, res, next) {
// ... perform some operations
next(); // Call next() so Express will call the next middleware function in the chain.
}

// Function added with use() for all routes and verbs
app.use(middleware_function);

// Function added with use() for a specific route
app.use('/route_path', middleware_function);

// A middleware function added for a specific HTTP verb and route
app.get('/', middleware_function);

app.listen(3000);

I hope this post has proved useful in introducing some of the fundamental operations and approaches that go into web servers, and that the relationship between two widely used back-end frameworks, Node and Express, can work seamlessly and in tandem to overcome some of the inherent challenges in serving a web application. Thank you for reading!

Sources:

--

--