Skip to content

How to Make API Requests with Request-Promise in Node.js

One task you’ll encounter often in Node.js is making HTTP requests to an external API from a server. Request-promise, a Promise-based wrapper for the popular request library, helps us do just that. This in-depth reference tutorial shows you how to use an NPM package called request-promise, which is an extended version of the request library with added Promise support.

By the end of this tutorial, you should be able to:

  • Learn how to install request-promise from NPM
  • Make GET, POST, PUT, PATCH, and DELETE calls to external APIs using request-promise
  • Configure your requests with headers, query-strings, bodies, and more options
  • Understand how to handle errors when making HTTP requests using request-promise

Goal

  • Learn how to make HTTP requests to external APIs using request-promise

Prerequisites

Watch: Make API Requests with request-promise

Overview

So you need to make an HTTP request to an API or external server from Node.js?

You could use the built-in http module in Node.js to accomplish that, but you’ll end up implementing a lot of standard features that are already in existing, more robust libraries. Also, http doesn’t support Promises.

We’re going to look at request-promise, a Promise-based wrapper for the popular request library, which helps us make HTTP requests and API calls.

The request package is one of the most popular HTTP request library for Node.js (with almost 16 million downloads per week at time of writing), but unfortunately it doesn’t support Promises either. That’s why we use a package called request-promise, which is an extended version of the request library with added Promise support.

The two packages work almost exactly the same, aside from Promise support and a few other minor differences.

There are plenty of other libraries with similar functions as request and request-promise (such as axios, got, and node-fetch), but we recommend using well-known packages with lots of examples and previous questions that can be found on the internet, whenever possible. The request and request-promise packages certainly meet that criteria, being very popular packages in the NPM ecosystem.

I should mention that request has entered maintenance mode, meaning that it will no longer be adding new features. The package isn’t deprecated, but it will stay the same as it is now with only minor bug fixes being merged in. The packages I mentioned above are modern alternatives to request that don’t need a wrapper library like request-promise, but request is still stable and highly depended upon within the ecosystem. Essentially, request won’t be the last HTTP client you learn to use, but it’s a very solid place to start that won’t be changing in the future.

Installation

To get started using request-promise, you need to install it from NPM, as well as the regular request package. The request-promise package relies on request as a peer-dependency, and must be installed directly into your project.

To get started, install them both:

Terminal window
npm install request request-promise

To begin using request-promise, require it in your code. I like to use rp as the shorthand name for request-promise, but you can call it whatever you like:

const rp = require("request-promise");

Make a request

The simplest request you can make involves passing a URL string to request-promise. This will make a GET request to the provided URL, and return a Promise that resolves with the body of the response:

const rp = require("request-promise");
rp("https://jsonplaceholder.typicode.com/todos")
.then(body => console.log("Here's the response body as a string!", body))
.catch(err => console.error(err));

The first argument passed to request-promise can be either a string URL, or an options object used to configure the request.

The options object is how you customize the request you’re sending, by specifying the URL, the method, any data you are sending with the request, and many other options.

Here is an example of making a more complicated GET request by passing an options object:

const rp = require("request-promise");
const options = {
url: "https://jsonplaceholder.typicode.com/todos",
method: "GET",
qs: {
userId: 1
},
headers: {
"User-Agent": "request"
},
json: true
};
rp(options)
.then(todos => console.log(todos))
.catch(err => console.error("Could not complete request", err));

Common options

There are lots of configuration options available, and you can read about all of them on the main request package’s documentation.

Here are some of the most commonly used options for configuring a request and sending data:

  • url: The address you are making an HTTP request to. Can also use the field uri, which does the same thing.
  • method: The name of the HTTP method to use when making the request (such as GET, POST, DELETE, etc).
  • body: The data being sent with the request, used with POST, PUT, and PATCH requests. This can be a String, Buffer, or Stream. Can also be an Object if json option is set to true and the object can be JSON-serializable.
  • headers: Object containing HTTP headers to set on the outgoing request.
  • qs: Object containing querystring values that will be appended to the URL, such as {userId: 1, limit: 20} which will be turned into http://example.com/?userId=1&limit=20.
  • json: Setting this to true will stringify the outgoing body field to JSON representation, and will parse incoming response bodies from JSON into a JS value. Set this to true when communicating with a JSON API.

All requests require at least a URL, and GET is the default method if you don’t specify a method. Not every request needs to have a body, query strings, or headers though. It depends on the request you’re making and what you need to send to the server.

Important things to remember

There are two important things to remember when working with request-promise which are not immediately obvious.

First, the request will resolve with the body value from the response when successful (2xx response code), not the full response object. If you need access to the full response object, you can pass resolveWithFullResponse: true in the options object when creating the request.

Second, the Promise is rejected if the server you’ve made a request to responds with a status code outside of the 2xx range. So if the server sends back a 401 response (not authorized), or a 500 (server error), the Promise will be rejected with a StatusCode error. To prevent this behavior, and have all completed requests resolve the Promise, set simple: false in the options object.

Bonus important thing: the request and request-promise libraries are meant to be used on the server side with Node.js — meaning they aren’t meant to run in the browser. If you need an HTTP library to make API calls from both the browser and the server, checkout axios or node-fetch.

Send JSON data to a server

To send data to a remote server, you’ll use a POST, PUT, or PATCH request, and send a payload in the body field of the request. In the case of a JSON API, you want to send a JSON payload to the server.

Let’s take a look at a basic POST request, where we are sending a JSON payload to a remote server:

const rp = require("request-promise");
const URL = "https://jsonplaceholder.typicode.com/todos";
const payload = {
userId: 1,
title: "This is a todo title",
completed: false
};
rp({
url: URL,
method: "POST",
body: payload,
json: true
})
.then(body => console.log("id", body.id))
.catch(err => console.error(err));

Using the url parameter and the method parameter, we’ve specified where and how to make the request. To send data to the server, we set the body field to the payload object we created. And finally, setting json: true tells request-promise to stringify the outgoing payload into a JSON string.

Because we set json: true, not only will the outgoing body field be turned into a JSON string, but the incoming response body will be parsed from JSON into a JavaScript data structure. This saves you from having to manually parse the returned JSON into an object yourself using JSON.parse().

The outgoing body must be serializable to JSON or else the promise will reject when we attempt to make the request.

Setting defaults

If you find yourself using the same parameters frequently when making requests, you can create an instance of request-promise which has those options already applied as defaults. This can be useful when you find yourself making requests with lots of configuration options, or you want to standardize how your requests are being made.

To set defaults, use the .defaults method and pass in your configuration options. Setting the baseUrl option allows you to later pass a uri field a string which will be appended to the baseUrl. Setting baseUrl is useful when you will be making multiple requests to the same domain, and will be changing the path on a per request basis.

const rp = require("request-promise");
const defaultRequest = rp.defaults({
baseUrl: "https://jsonplaceholder.typicode.com/",
json: true,
resolveWithFullResponse: true,
headers: {
"User-Agent": "request-promise"
}
});
defaultRequest("/todos").then(console.log);

Every request made with the defaultRequest instance above will have those options set. You can create multiple instances with different defaults if you wish. Setting defaults is useful when you want all requests to carry the same headers, base url, authorization, or when standardizing the request-promise client when creating an API client library.

Error handling

Many things can go wrong when making HTTP calls. The server you are contacting could be offline, the request needs to be authenticated, or maybe your internet connection has gone down. It’s important to handle these errors so we can react accordingly, even if that’s just logging the error and moving on.

When an error occurs when using request-promise, the Promise will reject with an error that contains information about what went wrong, and what type of error was encountered. Make sure you catch these errors (using either a try/catch block if using async/await, or with a regular catch handler attached to the Promise), and decide what to do with them.

There are two main kinds of errors you can encounter when using request-promise; RequestError and StatusCodeError.

You can determine what kind of error you have received by checking the name property on the error.

RequestError: problem executing the request itself, it failed before it was unable to reach the remote server, or was unable to complete the request. If your internet connection goes out, you’re likely to see this error get thrown.

StatusCodeError: response from the server was outside of the 2xx range that indicates a successful request. If the server sends you a 404 (Not Found) response, you’ll get a StatusCodeError to notify you about it. This error provides you with the offending status code, so you can tell exactly what went wrong if you need to.

If you want to handle specific error conditions, you can check the details of the error you received and decide how to respond:

const rp = require("request-promise");
// This will trigger a 404 response
rp("https://jsonplaceholder.typicode.com/pizza")
.then(body => console.log(body))
.catch(err => {
// logging the error
console.error(err)
if (err.name ==== "StatusCodeError") {
if (err.statusCode === 401) {
// NotAuthorized error, maybe refresh the user's auth token?
} else if (err.statusCode === 404) {
// NotFound, the server was unable to locate the resource
} else if (err.statusCode === 500) {
// Interal Server, something went wrong with the server itself!
}
} else if (err.name ==== "RequestError") {
// something went wrong in the process of making the request
// maybe the internet connection dropped
}
});

What your program does once it has received an error is up to you. The least you should do is make sure you catch the error and log it somewhere.

If you’d prefer to disable the behavior of having non 2xx status codes throw errors, set simple: false in the options object when making a request. This will make it so all completed responses will fulfill the Promise, regardless of their associated status code.

Recap

In this tutorial we explored the different ways to make HTTP requests using the request-promise module. Request-promise requires the request module to be installed as a dependency. To make an HTTP request, pass an options object to an instance of request-promise, which specifies at least what URL to make the request to, as well as the method, body, and any other configuration details. Status codes outside the 2xx range will reject the request’s Promise unless configured otherwise.

We went over the most common configuration options which help you make different kinds of requests, and demonstrated some examples of sending data to a server. We also covered how to set defaults for an instance of request-promise, and looked at how to handle errors when making requests.

This overview is a starting point for understanding how to make HTTP and API requests with Node.js. Although there are plenty of libraries out there to help you make HTTP requests, request-promise is my preferred choice because there are plenty of examples online.

Further your understanding

  • The requests being sent to the JSON Placeholder API are functional examples. Try them out!
  • Write a request that interacts with the JSON Placeholder API to retrieve post items for a specific user (you’ll need to use query strings to filter for a user id).
  • How would you convert the examples to using async/await? How would that affect error handling?

Additional resources