Create a Server with the Node.js HTTP Module
Tags: node, tutorial, webdev, http
Published: 2020-04-21 15:10:34

_📣 This post originally appeared as [Create a Server with the Node.js HTTP Module](https://blog.bearer.sh/create-node-http-server/) on [The Bearer Blog](https://blog.bearer.sh)._ 
<!--kg-card-begin: markdown-->

Using Node.js on the web generally involves a server framework, like [Express](https://expressjs.com/), [Hapi](https://hapi.dev/), or [Koa](https://koajs.com/). These make working with the underlying HTTP support easier. Sometimes you need the full power of a framework, but in other cases that can be overkill. In this article, we'll ignore the benefits of a framework and look at the underlying features of Node's HTTP module and how you can use it to create a web server. In future articles, we'll examine other features of the HTTP module.

## HTTP

`http` is one of the built-in modules that comes with Node.js. It provides low-level features for making requests as a client, as well as running a server to accept requests and return responses.

While it is full-featured from a technological standpoint, it can be difficult to use compared to many of the abstractions available today.

To better understand the base functionality that you are likely to interact with, let's use the `createServer` method to create a basic server and handle some common tasks.

_💡Note: In this article, we'll focus on `http` and not `https`. The `https` module includes all the core features of `http`, but with additional options for handling the necessary security differences like certifications._

## http.createServer

The `createServer` method allows Node.js to act as a web server and receive requests. A basic implementation may look like this:

```javascript
const http = require("http")
const PORT = 3000
const server = http.createServer()

server.listen(PORT, error => {
  if (error) {
    return console.error(error)
  }

  console.log(`Server listening on port ${PORT}`)
})
```

First, we import `http`. We also define a constant for the port. This may be familiar if you're used to the syntax of Express.

Next, set `server` equal to `http.createServer`. In this case, we call `createServer` on it's own without any arguments. You can pass in an `option` object, as well as a callback function to handle the requests. An alternate approach is to listen for events. More on both of these techniques shortly.

Finally, we tell the server to `listen` on the defined `PORT`, then pass a callback to handle any errors and print a message letting us know that the server is up and running.

This server doesn't do anything yet. If we try to visit `http://localhost:3000` or make a request to it, the request will eventually timeout since the server doesn't know that it needs to respond. Let's fix that by adding in some logic to handle incoming requests.

You can either listen to the `request` event, or pass a callback function into `createServer`. We'll show both:

**Event-based**

```javascript
const server = http.createServer()
server.on("request", (request, response) => {
  // handle requests
})
```

**Callback on createServer**

```javascript
const server = http.createServer((request, response) => {
  // handle requests
})
```

To keep things easier to follow, we'll use the second approach for the rest of this article. With that in mind, the techniques will translate.

The callback has `request` and `response`. As with any callback function, these names are only how you will reference each argument going forward. They map to [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_class_http_incomingmessage) and [`http.ServerResponse`](https://nodejs.org/api/http.html#http_class_http_serverresponse) respectively. These are all the basis for most third-party frameworks and libraries you may come across. To get a better understanding, let create a `GET` response to the `/cats` endpoint and return an array of cat names.

```javascript
const server = http.createServer((request, response) => {
  // [1]
  const { method, url, headers } = request

  // [2]
  if (method === "GET" && url === "/cats") {
    response.statusCode = 200
    response.setHeader("Content-Type", "application/json")
    const responseBody = {
      headers,
      method,
      url,
      body: ["Mrs. Meowsers", "Hairball", "Jerk"]
    }

    response.write(JSON.stringify(responseBody))
    response.end()
  }
})
```

The code above does a few things. It pulls out `method`, `url`, and `headers` from the request object _(1)_. Depending on what you need, you may also want to add additional properties to the destructured variables. It also checks if the method and url match what we're looking for _(2)_. Finally, it sets the status code and header, assembles a response body, and writes the body to the response before ending the response. In this case, the response body contains details about the request, as well as the data we want to send back. Before sending the response to the client, we need to stringify the data. This covers a basic `GET`, but what about more complex options like `POST`, querystrings, or running an HTML page? Here are a few common examples that can be useful.

### Parse a querystring

You can parse query strings a few ways. One technique is to use the built-in `url` module. For relative paths, it requires the url and the domain.

```javascript
// Given a request made to: http://localhost:3000/cats?id=1234
require("url")

//...
const { url, headers } = request
let urlParts = new URL(url, `http://${headers.host}`)

console.log(urlParts.searchParams.get("id"))
// => 1234
```

Take note of the _getter_ method on the end. `searchParams` returns a [URLSearchParams](https://nodejs.org/api/url.html#url_class_urlsearchparams) object. Interact with the search parameters through the `get` and `set` methods. You can also use the `searchParams.has()` method to confirm that a property exists before accessing it.

### Handling POST and PUT data

Managing incoming data is a little more complex, as incoming request data is a [Readable Stream](https://nodejs.org/api/stream.html#stream_class_stream_readable). Streams allow you to process data in chunks. This can be frustrating if you aren't expecting it, especially if you come from a background in more synchronous programming. You can avoid it through a variety of third-party modules from NPM, or process them using request events.

```javascript
//...
const server = http.createServer((request, response) => {
  let data = []
  request
    .on("data", d => {
      data.push(d)
    })
    .on("end", () => {
      data = Buffer.concat(data).toString()
      response.statusCode = 201
      response.end()
    })
})

/*
Note: For stringified data, you can also use string concatenation:
let data = ""
...
data += d
*/
```

The `request` has all the events available to [readable streams](https://nodejs.org/api/stream.html#stream_class_stream_readable). You can think of this like sending a book one word at a time, then assembling it. Whenever a "word" comes in, the `data` event fires and we save the word. Then when the transmission ends, all the parts are put together into the finished book.

### Sending HTML

Most of the examples so far have been API focused, but what if you want a route to return HTML?

```javascript
//...
if (method === "GET" && urlParts.pathname === "/") {
  response.setHeader("Content-Type", "text/html")
  response.statusCode = 200
  response.end("<html><body><h1>Hello, World!</h1></body></html>")
}
```

The same technique could work with a templating system, as long as the output is a string of HTML.

## Wrapping up

For more on creating a server, the official Node.js documentation has an excellent guide on [The Anatomy of an HTTP Transaction](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/).

If you plan to only manage a few routes, or even if your goal is to create your own custom-purpose framework, using `http` might be a good option. Many server frameworks use it under the hood to create their server instances. Express, for example, [passes it's core app](https://github.com/expressjs/express/blob/e1b45ebd050b6f06aa38cda5aaf0c21708b0c71e/lib/application.js#L616-L619) right into `createServer`:

```javascript
app.listen = function listen() {
  var server = http.createServer(this)
  return server.listen.apply(server, arguments)
}
```

Come back to the [Bearer Blog](https://blog.bearer.sh/) for more about Node.js' HTTP module, and check how you can monitor all third-party APIs and web services at [Bearer](https://bearer.sh).

<!--kg-card-end: markdown-->