How to create a web server in Node.js
© https://nodejs.org/en/

How to create a web server in Node.js

Use Node core APIs to create a minimal web server

ByMario Kandut

honey pot logo

Europe’s developer-focused job platform

Let companies apply to you

Developer-focused, salary and tech stack upfront.

Just one profile, no job applications!

This article is based on Node v16.15.1.

This article shows how to create a web server in Node.js with the core APIs. We are going to use the http module. In a real world scenario this is not recommended, and a web framework should be used. This article is only demonstrating that this is possible without a framework.

Create a minimal web server

💰 The Pragmatic Programmer: journey to mastery. 💰 One of the best books in software development, sold over 200,000 times.

A minimum viable web server would have the following requirements:

  • Responds to HTTP requests based on a HTTP verb (for example. GET)
  • Responds to request based on a given route
  • Responds with 404 status code, if route isn't found.
  • Sets applicable headers (for example: Content-Type:)

Next steps

  1. Basic Setup
  2. Create a basic web server
  3. Add routes and status codes

1. Basic Setup

Node.js has to be installed. Use the node version manager nvm to install Node.

Create a folder http-web-server and cd into it.

mkdir http-web-server
cd http-web-server

2. Create a basic web server

Now, create a file server.js.

touch server.js

And then copy the following code into it.

'use strict';
const http = require('http');
const PORT = process.env.PORT || 3000;

const helloWorld = `<html>
  <head>
    <style>
     body { background: white; margin: 1.5rem }
     h1 { color: red; font-family: sans-serif }
    </style>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>`;

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.end(helloWorld);
});

server.listen(PORT);

In the code above, we have used the http module to create a server with the createServer method. The createServer method is passed a function with two objects in, the request object and the response object. This function is called every time the server receives a request and the objects are created for every request. In the function we set the header to Content-Type: text/html and return the constant helloWorld to return the HTML string. The response object is a writable stream, because in the node core it inherits from stream.Stream), and calling end writes the content and closes the connection. The listen method binds the defined PORT to the object created from createServer.

Now execute the code with node server.js and open a browser tab with http://localhost:3000. You should see a Hello World in red on a white background.

The Node process will not exit by itself, because the created server keeps the process open.

3. Add routes and status codes

Our example is not meeting the criteria. Currently, for any route the same response is given, we don't return any status codes and have no error handling.

Let's update our server.js with a response based on route.

'use strict';
const http = require('http');
const url = require('url');

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

const helloWorld = `<html>
  <head>
    <style>
     body { background: white; margin: 1.5rem }
     h1 { color: red; font-family: sans-serif }
    </style>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>`;

const root = `<html>
  <head>
    <style>
     body { background: white; margin: 1.5rem }
     h1 { color: black; font-family: sans-serif }
     a { color: green; text-decoration: underline; }
    </style>
  </head>
  <body>
    <h1>ROOT</h1>
    <a href='/hello'>Hello</a>
  </body>
</html>`;

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  const { pathname } = url.parse(req.url);

  if (pathname === '/') {
    res.end(root);
    return;
  }

  if (pathname === '/hello') {
    res.end(helloWorld);
    return;
  }
});

server.listen(PORT);

Now with the help of the url module we are parsing the request url and returning a different HTML string based on the route. The parse method in the url module turns a URL string into an object containing various segments of the URL.

Restart your node server. When you navigate to http://localhost:3000/hello you should see in black ROOT and if you go to http://localhost:3000/hello you should see hello world red.

Now let's add the status codes and error handling for route not found and wrong HTTP verb.

'use strict';
const http = require('http');
const url = require('url');

const PORT = process.env.PORT || 3000;
const { STATUS_CODES } = http;

const helloWorld = `<html>
  <head>
    <style>
     body { background: white; margin: 1.5rem }
     h1 { color: red; font-family: sans-serif }
    </style>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>`;

const root = `<html>
  <head>
    <style>
     body { background: white; margin: 1.5rem }
     h1 { color: black; font-family: sans-serif }
     a { color: green; text-decoration: underline; }
    </style>
  </head>
  <body>
    <h1>ROOT</h1>
    <a href='/hello'>Hello</a>
  </body>
</html>`;

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html');
  const { pathname } = url.parse(req.url);

  // check http method
  if (req.method !== 'GET') {
    res.statusCode = 405;
    res.end(STATUS_CODES[res.statusCode] + '\r\n');
    return;
  }

  if (pathname === '/') {
    res.end(root);
    return;
  }

  if (pathname === '/hello') {
    res.end(helloWorld);
    return;
  }

  // no route found
  res.statusCode = 404;
  res.end(STATUS_CODES[res.statusCode] + '\r\n');
});

server.listen(PORT);

We have destructured the STATUS_CODES object, which contains the status codes from the http module, see docs. If the request method is not GET, we are returning the status code 405 (Method not allowed) and end the response with the status message from the key-values of STATUS_CODES.

If the route is not / or /hello, we return status code 404 (Page not found) and end the response with the applicable message from STATUS_CODES.

Restart your node server and check if the routes are handled correctly. For testing the wrong http verb, only GET is supported in our server, open postman or insomnia or use the following code to make a POST request with the http module

node -e "http.request('http://localhost:3000', {method: 'POST'}, (res) => res.pipe(process.stdout)).end()"

In any case the response should be Method not allowed.

The status code 200 (OK) is the default status code, hence, there is no need to set it for the routes / and /hello.

We have now met all criteria and have created a basic web server.

This approach is very rigid and cumbersome, especially when you try to add more functionality. Node.js frameworks like Express or Fastify enables us to write a more flexible, maintainable and declarative way.

TL;DR

  • It is possible to create a web server with the core modules of Node.js,

but the code becomes rigid and unmanageable.

  • In a real-world scenario you would write web servers with Node.js frameworks.

Thanks for reading and if you have any questions, use the comment function or send me a message @mariokandut.

If you want to know more about Node, have a look at these Node Tutorials.

References (and Big thanks):

NodeJS, NodeJS - http, JSNAD,

More node articles:

Getting started with Webpack

How to list/debug npm packages?

How to specify a Node.js version

How to create a web server in Node.js

How to dynamically load ESM in CJS

How to convert a CJS module to an ESM

How to create a CJS module

How to stream to an HTTP response

How to handle binary data in Node.js?

How to use streams to ETL data?

How to connect streams with pipeline?

How to handle stream errors?

How to connect streams with pipe?

What Is a Node.js Stream?

Handling Errors in Node (asynchronous)

Handling Errors in Node.js (synchronous)

Introduction to errors in Node.js

Callback to promise-based functions

ETL: Load Data to Destination with Node.js

ETL: Transform Data with Node.js

ETL: Extract Data with Node.js

Event Emitters in Node.js

How to set up SSL locally with Node.js?

How to use async/await in Node.js

What is an API proxy?

How to make an API request in Node.js?

How does the Event Loop work in Node.js

How to wait for multiple Promises?

How to organize Node.js code

Understanding Promises in Node.js

How does the Node.js module system work?

Set up and test a .env file in Node

How to Use Environment Variables in Node

How to clean up node modules?

Restart a Node.js app automatically

How to update a Node dependency - NPM?

What are NPM scripts?

How to uninstall npm packages?

How to install npm packages?

How to create a package.json file?

What Is the Node.js ETL Pipeline?

What is data brokering in Node.js?

How to read and write JSON Files with Node.js?

What is package-lock.json?

How to install Node.js locally with nvm?

How to update Node.js?

How to check unused npm packages?

What is the Node.js fs module?

What is Semantic versioning?

The Basics of Package.json explained

How to patch an NPM dependency

What is NPM audit?

Beginner`s guide to NPM

Getting started with Node.js

Scroll to top ↑