Using a REST API

REST APIs are extremely popular on the web and allow you to freely grab a site's data if it has an available API over an HTTP connection.

Making HTTP requests with Node

In these examples, we will be using undiciopen in new window, an excellent library for making HTTP requests.

To install undici, run the following command:

npm install undici
yarn add undici
pnpm add undici

Skeleton code

To start off, you will be using the following skeleton code. Since both the commands you will be adding in this section require an interaction with external APIs, you will defer the reply, so your application responds with a "thinking..." state. You can then edit the reply once you got the data you need:

const { Client, Intents, MessageEmbed } = require('discord.js');

const client = new Client({ intents: [Intents.FLAGS.GUILDS] });

client.once('ready', () => {
	console.log('Ready!');
});

client.on('interactionCreate', async interaction => {
	if (!interaction.isCommand()) return;

	const { commandName } = interaction;
	await interaction.deferReply();
	// ...
});

client.login('your-token-goes-here');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

TIP

We're taking advantage of destructuring in this tutorial to maintain readability.

Using undici

Undici is a Promise-based HTTP/1.1 client, written from scratch for Node.js. If you aren't already familiar with Promises, you should read up on them here.

In this tutorial, you will be making a bot with two API-based commands using the random.catopen in new window and Urban Dictionaryopen in new window APIs.

On top of your file, import the library function you will be using:

const { request } = require('undici');
1

Retrieving the JSON response from a request

To get the data from within the response object, you can define the following helper function (it concatenates all the body chunks and parses it as an object) above your client events. Note that the function returns a Promise you need to handle.

async function getJSONResponse(body) {
	let fullBody = '';

	for await (const data of body) {
		fullBody += data.toString();
	}

	return JSON.parse(fullBody);
}
1
2
3
4
5
6
7
8
9

Random Cat

Random cat's API is available at https://aws.random.cat/meowopen in new window and returns a JSONopen in new window response. To actually fetch data from the API, you're going to do the following:

const catResult = await request('https://aws.random.cat/meow');
const { file } = await getJSONResponse(catResult.body);
1
2

If you just add this code, it will seem like nothing happens. What you do not see, is that you are launching a request to the random.cat server, which responds some JSON data. The helper function parses the response data to a JavaScript object you can work with. The object will have a file property with the value of a link to a random cat image.

Next, you will implement this approach into an application command:

client.on('interactionCreate', async interaction => {
	// ...
	if (commandName === 'cat') {
		const catResult = await request('https://aws.random.cat/meow');
		const { file } = await getJSONResponse(catResult.body);
		interaction.editReply({ files: [file] });
	}
});


 
 
 
 
 

1
2
3
4
5
6
7
8

So, here's what's happening in this code:

  1. Your application sends a GET request to random.cat.
  2. random.cat sees the request and gets a random file url from their database.
  3. random.cat then sends that file's URL as a JSON object that contains a link to the image.
  4. undici receives the response and you parse it with getJSONResponse().
  5. Your application then attaches the image and sends it in Discord.

Urban Dictionary

Urban Dictionary's API is available at https://api.urbandictionary.com/v0/defineopen in new window, accepts a term parameter, and returns a JSON response.

The following code will fetch data from this api:

// ...
client.on('interactionCreate', async interaction => {
	// ...
	if (commandName === 'urban') {
		const term = interaction.options.getString('term');
		const query = new URLSearchParams({ term });

		const dictResult = await request(`https://api.urbandictionary.com/v0/define?${query}`);
		const { list } = await getJSONResponse(dictResult.body);
	}
});
 



 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11

Here, you are using JavaScript's native URLSearchParams classopen in new window to create a query stringopen in new window for the URL so that the Urban Dictionary server can parse it and know what you want to look up.

If you were to do /urban hello world, then the URL would become https://api.urbandictionary.com/v0/define?term=hello%20world since the string "hello world" is encoded.

You can get the respective properties from the returned JSON. If you were to view it in your browser, it usually looks like a bunch of mumbo jumbo. If it doesn't, great! If it does, then you should get a JSON formatter/viewer. If you're using Chrome, JSON Formatteropen in new window is one of the more popular extensions. If you're not using Chrome, search for "JSON formatter/viewer <your browser>" and get one.

Now, if you look at the JSON, you can see that it has a list property, which is an array of objects containing various definitions for the term (maximum 10). Something you always want to do when making API-based commands is to handle the case when no results are available. So, if you throw a random term in there (e.g. njaksdcas) and then look at the response the list array should be empty. Now you are ready to start writing!

As explained above, you'll want to check if the API returned any answers for your query, and send back the definition if that's the case:

if (commandName === 'urban') {
	// ...
	if (!list.length) {
		return interaction.editReply(`No results found for **${term}**.`);
	}

	interaction.editReply(`**${term}**: ${list[0].definition}`);
}


 
 
 

 

1
2
3
4
5
6
7
8

Here, you are only getting the first object from the array of objects called list and grabbing its definition property.

If you've followed the tutorial, you should have something like this:

User used /urban
Guide Bot Bot 09/21/2022
@User, No results for njaksdcas
User used /urban
Guide Bot Bot 09/21/2022
hello world: The easiest, and first program any newbie would write. Applies for any language. Also what you would see in the first chapter of most programming books.

Now, you can make it an embed for easier formatting.

You can define the following helper function at the top of your file. In the code below, you can use this function to truncate the returned data and make sure the embed doesn't error, because field values exceed 1024 characters.

const trim = (str, max) => (str.length > max ? `${str.slice(0, max - 3)}...` : str);
1

And here is how you can build the embed from the API data:

const [answer] = list;

const embed = new MessageEmbed()
	.setColor('#EFFF00')
	.setTitle(answer.word)
	.setURL(answer.permalink)
	.addFields({ name: 'Definition', value: trim(answer.definition, 1024) }, { name: 'Example', value: trim(answer.example, 1024) }, { name: 'Rating', value: `${answer.thumbs_up} thumbs up. ${answer.thumbs_down} thumbs down.` });

interaction.editReply({ embeds: [embed] });
1
2
3
4
5
6
7
8
9

Now, if you execute that same command again, you should get this:

User used /urban
Guide Bot Bot 09/21/2022
Definition
The easiest, and first program any newbie would write. Applies for any language. Also what you would see in the first chapter of most programming books.
Example
programming noob: Hey I just attended my first programming lesson earlier!
.NET Veteran: Oh? What can you do?
programming noob: I could make a dialog box pop up which says "Hello World!" !!!
.NET Veteran: lmao.. hey guys! look.. check out this "hello world" programmer

Console.WriteLine("Hello World")
Rating
122 thumbs up.
42 thumbs down.

Resulting code

If you want to compare your code to the code we've constructed so far, you can review it over on the GitHub repository here open in new window.