How to Use Timeouts in Node.js
Tags: node
Published: 2023-11-15 12:32:29

Because of the asynchronous nature of Node.js, it's crucial you set timeouts to ensure the responsiveness of your application.

Node.js timeouts help prevent indefinite waiting and let your backend handle situations where tasks take longer than expected. Thanks to timeouts, you can control the maximum duration allowed for incoming and outgoing requests.

In this article, we'll look at the different types of timeouts and how to set them in vanilla Node.js and Express. We'll also see how to use timeouts in some of the most popular Node.js libraries, such as Axios and Sequelize.

It's time to learn how to deal with idle scenarios in Node.js!

## Types of Timeouts in Node.js

A Node.js backend application receives requests from clients and makes requests to databases, third-party services, or other backends. To ensure high performance and scalability, these requests are usually non-blocking and asynchronous. In other words, Node.js revolves around the efficient handling of asynchronous tasks.

Timeouts play a key role in Node.js because requests could hang forever without them, leading to resource exhaustion and bottlenecks. By instead giving each asynchronous task a specific time to complete, you can avoid drawbacks while maintaining responsiveness.

There are two types of Node.js timeouts:

- **Incoming request timeouts**: To prevent a Node.js application from staying busy indefinitely while trying to generate a response to a client request.
- **Outgoing request timeouts**: These prevent the Node.js server from being blocked if a request to an external service has a response delay.

Keep in mind that there is no universal timeout value that fits every scenario. A value that's too low can cause unnecessary errors, while a value that's too high can reduce an application's responsiveness. The right timeout depends on the type of operation you perform and the performance prerequisites.

Let's now dig into how to deal with the two types of Node.js timeouts.

## Dealing with Timeouts in Incoming Requests

When a client makes a request to the server, a connection between the two is established. Incoming request timeouts specify how long these client connections can last without any data being sent or received.

Based on the direction of data transmission, there are three types of Node.js timeouts for incoming requests:

- **Request timeouts**: If the server receives no data from the client within the specified timeout, it assumes that the client is unresponsive and terminates the connection.
- **Response timeouts**: If the server doesn't manage to produce a response within the timeout, it closes the connection.
- **Socket timeouts**: If the server doesn't send or receive data within the timeout, it closes the socket.

As the server sets these Node.js timeouts to handle client requests, they're also called server timeouts.
Thanks to them, Node.js can ensure efficient resource usage and prevent scenarios where connections remain open indefinitely or for no reason. That's especially useful when dealing with clients that are idle or willing to wait forever for a response.

Let's now look at setting incoming request timeouts in both vanilla Node.js and Express.

### Request Timeouts in Node.js and Express

Node.js exposes the [`server.requestTimeout`](https://nodejs.org/api/http.html#serverrequesttimeout) property to specify the timeout value in milliseconds for receiving an entire request from the client.

By default, it is set to `300000`, which means that Node.js waits up to 300 seconds for the client to send all the data.
If the timeout expires, the server responds with a [`408 Request Timeout`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) HTTP error code before closing the connection.

Change this behavior in vanilla Node.js by manually overriding `server.requestTimeout`:

```javascript
const http = require("http");

const hostname = "localhost";
const port = 3000;

// create a Node.js HTTP server
const server = http.createServer((request, response) => {
  // your APIs...
});

// make the Node.js server listen on port 3000
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

// set the request timeout to 20 seconds
server.requestTimeout = 20000;
```

If you are an Express user, set the property to the `server` object returned by `app()`:

```javascript
const express = require("express");

const port = 3000;

// initialize an Express server
const app = express();

// start the Express server
const server = app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

// specify a socket timeout of 10 seconds
server.requestTimeout = 10000;
```

To change the request timeout on a specific route, call the [`request.setTimeout()`](https://nodejs.org/api/http.html#requestsettimeouttimeout-callback) function as shown below:

```javascript
app.get("/api/your-api-path", (request, response) => {
  // set a request timeout of 5 seconds
  request.setTimeout(5000, () => {
    response.status(408);
    response.send("Request timeout");
  });

  // business logic...

  response.send("Hello, World!");
});
```

The Express server will now wait for incoming data for up to 5 seconds on the `/api/your-api-path` before producing a custom `408 Request Timeout` error.

### Response Timeouts in Node.js and Express

Node.js doesn't provide a function or property to set a global response timeout. However, you can specify it locally to a particular route with the [`response.setTimeout()`](https://nodejs.org/api/http.html#responsesettimeoutmsecs-callback) function:

```javascript
app.get("/api/get-data-from-cms", (request, response) => {
  // set a response timeout of 3 seconds
  response.setTimeout(3000, () => {
    response.status(504);
    response.send("Gateway Timeout");
  });

  // business logic...

  response.send("Hello, World!");
});
```

The Node.js Express backend will now be forced to produce a response for the `get-data-from-cms` endpoint before 3 seconds. Otherwise, it will fail with a [`504 Gateway Timeout`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504) error.

### Socket Timeouts in Node.js and Express

The Node.js [`server.timeout`](https://nodejs.org/api/http.html#servertimeout) property indicates the number of milliseconds of inactivity before a socket is presumed to have timed out. A socket is considered inactive when no data is being transferred in either direction within the specified timeout. By default, it is set to `0`, meaning that there are no timeouts and connections can hang forever.

To avoid that in vanilla Node.js, set `server.timeout` to a more suitable value as follows:

```javascript
const http = require("http");

const hostname = "localhost";
const port = 3000;

// create a Node.js HTTP server
const server = http.createServer((request, response) => {
  // your APIs...
});

// specify a socket timeout of 10 seconds
server.timeout = 10000;

// make the Node.js server listen on port 3000
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
```

The above snippets override the default Node.js server timeout by setting it to 10 seconds. Now, the connection will be closed if the server doesn't produce a response or hear from the client within that timeout.

In Express, set the `timeout` property on the `server` object returned by `app()`:

```javascript
const express = require("express");

// initialize an Express server
const app = express();

// start the Express server
const server = app.listen(3000, () => {
  console.log(`Server listening on port ${port}`);
});

// specify a socket timeout of 10 seconds
server.timeout = 10000;
```

If you need to perform specific operations before the socket gets closed, listen for the [`timeout`](https://nodejs.org/api/http.html#event-timeout) event:

```javascript
server.on("timeout", (socket) => {
  // custom logic to handle server timeouts (e.g., logging, ...)

  socket.destroy();
});
```

The Node.js runtime will run the callback passed to the event listener whenever a socket connection times out. Ensure you call [`socket.destroy()`](https://nodejs.org/api/net.html#socketdestroyerror) in this function so that the socket is closed and its resources are released as expected.

A similar way to achieve the same result is with the [`server.setTimeout()`](https://nodejs.org/api/http.html#serversettimeoutmsecs-callback) function:

```javascript
server.setTimeout(15000, (socket) => {
  // custom logic to handle server timeouts (e.g., logging, ...)

  socket.destroy();
});
```

In the above example, `setTimeout()` sets a timeout of 15 seconds. When a socket timeout occurs, Node.js calls the optional callback passed as a parameter. If no callback is present, it emits a `"timeout"` event and lets a `"timeout"` event listener intercept it.

## Dealing with Timeouts in Outgoing Requests

A Node.js application usually needs to connect to other backends, third-party services, or databases to retrieve and write data. That's what the microservice architecture is all about. When performing these outgoing requests, it's essential to apply some timeouts, as external servers may experience slowdowns and networks are sometimes unreliable. Since the Node.js application acts as a client to other servers here, these timeouts are also called client timeouts.

If you don't rely on timeouts, you're subject to the processing time imposed by other services, which can be long and affect your application's performance. Thus, it's crucial to always set a maximum time for each outbound request before sending it out.

Let's learn how to set Node.js timeouts for HTTP and database requests.

### Request Timeout with the Fetch API

The [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) was added to Node.js in version 17.5, and the [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) function is now the recommended way to perform HTTP requests. Since it's now officially part of the Node.js standard API, you can use it directly in your code without an import.

By default, `fetch()` doesn't involve timeouts, but you can define them through the [`AbortSignal.timeout()`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static) function:

```javascript
try {
  const response = await fetch("https://example.com/your-api-endpoint", {
    // headers, data, configs ...
    signal: AbortSignal.timeout(5000), // set a client timeout of 5 seconds
  });

  // handle response...
} catch (error) {
  // log HTTP, network, or timeout errors
  console.error(error);
}
```

If you aren't familiar with [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), it's an interface to abort requests from the Fetch API. In this case, `fetch()` will automatically cancel the request if the server doesn't respond within 5 seconds.

When the timeout is hit, Fetch will throw the following error:

```bash
[TimeoutError]: The operation was aborted due to timeout
```

If you don't want this error to crash your application, handle it by wrapping `fetch()` requests that involve timeouts with `try-catch` blocks.

### Request Timeout in Axios

[Axios](https://www.npmjs.com/package/axios) is one of the most used HTTP clients for Node.js. The package has a default timeout of `0`, which means no timeout. However, it offers the `timeout` setting so you can easily customize that behavior.

You can set a client timeout globally, as below:

```javascript
const axios = require("axios");

const axiosInstance = axios.create({
  timeout: 3000,
  // other configs...
});
```

Now, all HTTP requests made through `axiosInstance` will wait up to 3 seconds for a response before timing out.

Similarly, you can also specify a timeout locally for a single request:

```javascript
const axios = require("axios");

// ...

try {
  const response = await axios.get("https://example.com/your-api-endpoint", {
    // headers, data, configs ...
    timeout: 3000,
  });

  // handle response...
} catch (error) {
  // log HTTP, network, or timeout errors
  console.error(error);
}
```

In case of a timeout, `axios` will raise the following error:

```bash
AxiosError: timeout of 3000ms exceeded
```

### Timeout in Sequelize

Sequelize is the most popular ORM for Node.js. Its extensive API allows you to abstract the database layer, whether the DMBS is Oracle, Postgres, MySQL, MariaDB, SQLite, or SQL Server.

When initialized through the `Sequelize()` constructor, the library creates a pool of database connections. A [connection pool](https://en.wikipedia.org/wiki/Connection_pool) is a cache of database connections that Sequelize uses to avoid creating a new connection for each request, which would take time and resources.

Before executing a query, Sequelize tries to acquire a connection from the pool. If all connections in the pool are currently in use, it waits for a connection to become available. If no connection is free before the default timeout of 60 seconds, the ORM throws an error, and the query fails.

To avoid that, configure the connection pool in the Sequelize constructor as follows:

```javascript
const sequelize = new Sequelize(/* db connection configs */, {
  // other configs..
  pool: {
    acquire: 120000,
    // other pool configs...
  }
})
```

The `acquire` property in the `pool` object specifies the maximum time in milliseconds that Sequelize will try to get a connection before throwing a [`ConnectionAcquireTimeoutError`](https://sequelize.org/api/v6/class/src/errors/connection/connection-acquire-timeout-error.ts~connectionacquiretimeouterror).
In the above example, the pool will wait up to 120 seconds before running each query or transaction.

Congrats! You are now a master of Node.js timeouts!

## Wrapping Up

In this blog post, we explored why timeouts are so important in Node.js and how to set them in common scenarios.

You now know:

- What types of timeouts exist in Node.js
- How to set timeouts for incoming requests from clients
- How to set timeouts for outgoing requests to external services

Thanks for reading!

**P.S. If you liked this post, [subscribe to our JavaScript Sorcery list](https://blog.appsignal.com/javascript-sorcery) for a monthly deep dive into more magical JavaScript tips and tricks.**

**P.P.S. If you need an APM for your Node.js app, go and [check out the AppSignal APM for Node.js](https://www.appsignal.com/nodejs).**