How to Use Timeouts in Node.js

Antonello Zanini - Nov 15 '23 - - Dev Community

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 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 HTTP error code before closing the connection.

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

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;
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

To change the request timeout on a specific route, call the request.setTimeout() function as shown below:

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!");
});
Enter fullscreen mode Exit fullscreen mode

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() function:

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!");
});
Enter fullscreen mode Exit fullscreen mode

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 error.

Socket Timeouts in Node.js and Express

The Node.js server.timeout 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:

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}/`);
});
Enter fullscreen mode Exit fullscreen mode

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():

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;
Enter fullscreen mode Exit fullscreen mode

If you need to perform specific operations before the socket gets closed, listen for the timeout event:

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

  socket.destroy();
});
Enter fullscreen mode Exit fullscreen mode

The Node.js runtime will run the callback passed to the event listener whenever a socket connection times out. Ensure you call socket.destroy() 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() function:

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

  socket.destroy();
});
Enter fullscreen mode Exit fullscreen mode

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 was added to Node.js in version 17.5, and the 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() function:

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);
}
Enter fullscreen mode Exit fullscreen mode

If you aren't familiar with 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:

[TimeoutError]: The operation was aborted due to timeout
Enter fullscreen mode Exit fullscreen mode

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 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:

const axios = require("axios");

const axiosInstance = axios.create({
  timeout: 3000,
  // other configs...
});
Enter fullscreen mode Exit fullscreen mode

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:

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);
}
Enter fullscreen mode Exit fullscreen mode

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

AxiosError: timeout of 3000ms exceeded
Enter fullscreen mode Exit fullscreen mode

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 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:

const sequelize = new Sequelize(/* db connection configs */, {
  // other configs..
  pool: {
    acquire: 120000,
    // other pool configs...
  }
})
Enter fullscreen mode Exit fullscreen mode

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.
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 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .