Lesson Overview

Routing is the process of deciding how your application should respond to different types of requests. When a request comes in, your server needs to decide what function(s) should process that request. For example:, an app would route a request to a home page differently than it would route a request to submit login credentials. Your code can evaluate the type and URL of the request and send an appropriate response based on that information.

We would typically not route requests using Node.js alone: it's not great when you have many different types of files or want to handle POST requests. However, we will explore a basic example so you can better understand how it works (and why we need frameworks like Express.js)!


Before doing this lesson, make sure you've tried all the examples in the First Node.js Apps lesson.

Routing Requests with Node.js

In the previous lesson we wrote a web application that had only one response to any request to localhost:3000. However, in a real application you would do a lot more. For example:

How would this work? You would check the request's information and then process the request and send a response based on that information. Often you would look for patterns in the URL (e.g. if the URL contains "getyear" or if the request URL is /info). We tried to simulate this with the simple route handler we wrote. However, this program only sent a customized response containing a heading. Ideally we should send an appropriate HTML page. We'll do a simple version of this now.

First, create a new app. I'll call mine /route_handler_better.

Add an app.js file. Add sub-directories /public and /views. We'll use the public directory to contain any CSS and images you want in your application's pages. We'll use /views to store any HTML files that are part of our project.

In the /public directory, add the standards /css and /images directory. Add a CSS file and some images to your directories. In the /views directory, add an index.html and a second .html page. If you prefer, you can use these files: route handler views and public directories zip file (click the Download button on the right beside the "Raw" button).

Now start coding your app.js file: add the constant variables for the port number, the http module, and also the file system (fs) module:

"use strict";

const port = 3000,
    http = require("http"),
    fs = require("fs"); 

This program is going to use two helper functions: The first one will read in a file and send it as a response, or it will send an error response if a file can't be read. The second function simply encapsulates the error response code so we can re-use it.

Paste the two functions into your app.js file. A good review exercise would be to document each statement.

const customReadFile = (path, res) => {

    if (fs.existsSync(path)) {
        fs.readFile(path, (error, data) => {

            if (error) {
                sendErrorResponse(res, path);
    } else {  
        sendErrorResponse(res, path);
const sendErrorResponse = (res, path) => {
    res.writeHead(404, {
        "Content-Type": "text/html"
    res.write(`<h1>File Not Found!</h1><p>${path}</p>`);

The customReadFile() function accepts the file you want to read and the response object. We will have to pass the response object into this function because the function is going to write to the response body and send it.

The fs.existsSync(path) function checks the file system to see if path (a directory/file name) exists. It returns true if it exists and false if it doesn't exist. This allows us to only fetch files that actually exist.

Inside the fs.existsSync() if block, we read the file using the fs.readFile() function we became familiar with in the First Node.js Apps lesson. In this case, we attempt to read the file and if there is a problem reading the file, we log an error to the console and then we invoke the sendErrorResponse() function. I added a return statement afterwards, which exits the customReadFile() function at this point.

If there are no problems reading the file, we write the file to the response object and send the response.

If the file doesn't exist, we invoke the same sendErrorReponse() function. This function takes the provided response object and sends a 404 error response to the client.

Now we can set up the server so that it uses the customReadFile() function whenever a new request comes in. Go ahead and add the createServer() and listen() functions:

http.createServer((req, res) => {

}).listen(port, () => {
    console.log(`The server is listening on port number: ${port}`);

Our server will accept a request and then decide which file to return based on the request URL. Recall that when a page contains external resources such as client-side scripts, stylesheets, and images, each resource triggers a new request to the browser: we will need to handle all of these requests: If a file name ends with .html, we will find the appropriate HTML file and send it in the response as content-type text/html. If the file requested ends with .css, we will find the appropriate CSS file and send it in the response as content-type text/css. We will do this for each type of file in our project. Note the image types for the images you chose: the ones I provided are .jpg images but check your own, also. If you have .png or .gif, you'll have to add code to handle those.

First, we should set up variables for the path/name of the file being requested and also the content type header we will eventually set:

let contentType = ""; 
let path = "";

Next, we need to extract the file name from the end of the request URL. We can use split("/") to split the request URL by the forward slash character; the last element will be the file name.

let fileName = req.url.split("/").pop();

Now we'll use a if/elseif block to get the correct path and content type for the file that was requested:

if (req.url.indexOf(".html") >= 0) {
  contentType = "text/html";
  path = `./views/${fileName}`;
} else if (req.url.indexOf(".css") >= 0) {
  contentType = "text/css";
  path =  `./public/css/${fileName}`;
} else if (req.url.indexOf(".jpg") >= 0) {
  contentType = "image/jpg";
  path =  `./public/images/${fileName}`;

Note that if you have other types of files such as .png or .gif, you'll need to add an else-if block for those and set the appropriate content type.

In this if statement, we are ensuring that we set the path to the correct location of the requested file: your urls and references in your code may not exactly match the physical structure, and that's fine. For example, our stylesheets are in ./public/css but the <link> element inside the HTML files have the source as href="css/main.css". This is normal, because a Node.js project is not public, so it doesn't have the same directory structure. The path "css/main.css" or even "./css/main.css" wouldn't work in our project, because all css files are in the /public directory.

Let's add some console output for debugging purposes:


If we get through this multi-sided if and the contentType is still empty, it means that we didn't find a matching route for the requested file, so we will send the error response.

if (contentType === "") {
  sendErrorResponse(res, path);
} else {


But if this is false and we do have something in the contentType variable, it's safe to send a response containing the file and a 200 Ok status code:

if (contentType === "") {
  sendErrorResponse(res, path);
} else {
  res.writeHead(200, {
    "Content-Type": contentType
  customReadFile(path, res); 

That's it! Now save everything and if run npm init on your project if you haven't already done it. Then run your program (node app or nodemon app.js or npm start).

Everything should work: you should be able to click links, see the css styling, images, etc.

Now that you've tried this newer version of the routing program, you can probably still see some issues with it:

You can see that this is going to get tedious for even small projects, but also it's just not enough. We need to be able to do more, like POST requests, database/file writes, etc.

To fix this, we can use a framework like Express.js. Express.js can handle POST requests, write to a database or file, and much more. This will allow us to use the MVC (Model View Controller) design pattern create applications with dynamic data and much more functionality. This also means we can write better code for route handling.