Discord is one of the best platforms for developers to communicate for multiple reasons: the ability to create your own community (or server), talk to others using voice channels, and overall an amazing instant messaging platform. However, with the help of bots, the experience just becomes way more interesting and easier thanks to the automation and features these bots provide. There's a bot for pretty much everything. May it be for helping you manage your server or just sending you memes.

In this article, we'll be building our own Discord bot using discord.js which will allow us to program our own bot using NodeJS. If you're not familiar with NodeJS or JavaScript in general, I'd highly encourage you to check those out first so that you can follow along with ease. Regardless, I'll still be explaining all of the code I write for your understanding. Let's get started!


What we're building

Today, we'll be building a simple Discord bot that will fetch the price of various cryptocurrencies to get familiar with discord.js. But you can feel free to play around and build something else as well since the concepts will remain the same. I'll be using an API to fetch the prices so you can use a different API for a completely different purpose.

Our aims for today will be:

  • Register our bot to Discord and get the bot token
  • Add a command to ping our bot and check if it’s working
  • Add a command to our bot which will fetch the price of a cryptocurrency with respect to another currency or cryptocurrency
  • Add a command to get the latest news related to cryptocurrencies

Getting Bot Token from Discord

The first step towards creating our own bot is to register it with Discord and get a token to access it. So, let's head on over to the Discord Developer Portal and fill in some details. You'll need to login into your Discord account if you haven’t already.

Once logged in, you'll be greeted with a screen like this:

To create a bot, press the New Application button on the top right corner of your screen and you'll be asked to enter a name for your bot. I'll be naming it 'Crypto Bot' for now.

Now you can customize all the general information such as name, description, and icon as per your liking. Make sure to save your changes. Now, click on the Bot tab present in the settings sidebar on the left side.

On the bot page, you will see a screen like this from which you can copy or regenerate your token.

Copy the token and make sure you keep it safe and secure. This is a private token which you should not share with anyone and especially not put it up on GitHub or else people will be able to take actions on behalf of your bot, which you certainly don't want.

Finally, let's authorize our bot and add it to our server. For that, click on the OAuth2 tab and tick on the bot scope in the scopes section and send messages & embed links permissions in the bot permissions section.

Copy the OAuth2 link and open it up on your browser. You'll be asked to select the server in which you want to bot to join. Make sure you have permission to add bots to your server.

Awesome, we're ready to get started with programming our bot with NodeJS & Discord.js in the next section.


Initial Setup

Now that we have our bot token, we can get started with the code. Make a directory for your code and change into that directory on your terminal.

mkdir crypto-discord-bot
cd crypto-discord-bot

Inside this directory, use the npm init command to set up an NPM package and generate the package.json file.

For this project, we'll be having 2 dependencies: discord.js, a library that will help us to interact with the Discord API easily, and dotenv, which we'll be using to create environment variables for storing our bot token and other API keys. Let's go ahead and install those using:

npm install discord.js dotenv

Go ahead and open up the folder using your favorite text editor or IDE and let's begin coding.

Start by creating 3 new files in the project folder: bot.js, which will contain the source code for our Discord bot,.env, for storing confidential information and API keys, and .gitignore to exclude the .env and node_modules folder from being pushed to our GitHub. If you're not going to initialize Git, you can skip the .gitignore file.

Our folder structure looks something like this now:

crypto-discord-bot
	- bot.js
	- .env
	- .gitignore
	- node_modules
	- package.json
	- package-lock.json

Inside .gitignore, add the following lines of code:

node_modules/
.env

Now, inside the .env file, let's paste the bot token that we got from Discord. In case you lost the token, you can go back to the Discord Developer Portal and copy your token again. This is how your .env file should look like:

DISCORD_BOT_TOKEN = ODE2NTk2MTQwOTY2ODA2NTM1.YD9Qaw.uTwkqds9EIapoJ_zJQGX2PAYOWw

DISCORD_BOT_TOKEN is like a variable whose value can be accessed through process.env.DISCORD_BOT_TOKEN. These are known as environment variables. If you’re wondering why we are using environment variables to store our bot token, it’s mainly because we don’t want to reveal the token as others can misuse it. Make sure you do not reveal your bot token to anyone or upload it onto the internet.

Time for some actual JavaScript code.


Setting Up Our  Bot

If you notice, our bot is currently offline on our server. To make it online, let's write some code which I'll explain shortly.

// Require dependencies
const { Client } = require('discord.js');
const dotenv = require('dotenv');

// Load environment variables
dotenv.config();

// Create a bot instance
const bot = new Client();

// Log our bot in
bot.login(process.env.DISCORD_BOT_TOKEN);

As soon as you run this file using node bot, you'll see that our bot becomes online and we have successfully logged in. In case you see an error, please make sure that you've spelled the environment variable just like you did in the .env file.

In the first few lines of the code, all I did was just require the dependencies which our bot needs which are the discord.js library and dotenv . Then, to use environment variables in our file, we need to first load it by calling the .config() method on dotenv. As you can see, I've only imported the Client class from discord.js library since that's all we need for our purpose. Our bot will act as the client through which we’ll interact with Discord.

Let's move on to logging in as our bot. To do that, I've made an instance of the Client class and assigned it to a constant named bot. We can call several methods on the bot instance. For logging in, we use the .login() method which takes the bot token as a required parameter. Instead of pasting the bot token directly, we can access it from the .env file.

Commit till this part


The Ping Command

Here we go, our first command for our bot: !ping. This command is used to check if the bot is working or not. If it is working, the bot will reply back with 'I am working’ to let us know. In case we don't receive any reply from our bot, we can safely assume that there was some kind of error or our bot is down. Let's break this down into small tasks:

  1. Log to the console when the bot is ready
  2. Listen for messages and check if it matches any of our commands
  3. Check if the message was sent by a user or a bot
  4. Respond appropriately to the message

In Discord, every action can be considered an event. So we can listen for messages using the .on() method listening for message on the client and passing it an async (since we'll be making API calls in the upcoming commands and even discord.js returns promises) callback function with message as the parameter, like this: bot.on('message', async (message) => ...do something).

Let us first tackle our first subtask, which is to log to the console when our bot has logged in and is ready to be used. This is pretty simple as we only have to listen for the ready event and console.log() in our callback function. The code will be somewhat like this:

// Log to console when the bot is ready
bot.on('ready', () => {
  console.log(`${bot.user.username} is up and running!`);
});

Now, let's listen for messages and respond to the !ping command.

// Reply to user messages
bot.on('message', async (message) => {
  // Do not reply if message was sent by bot
  if (message.author.bot) return;

  // Reply to !ping
  if (message.content.startsWith('!ping')) {
    return message.reply('I am working!');
  }
});

In the above snippet of code, we have an event listener listening for message event. Once a message has been sent to the channel, we first check if the message's author is a bot or not. We can do this by accessing message.author.bot. This will return true if the message was sent by a bot and false if it was sent by a user.

Then, we check if the message starts with !ping which is our command for pinging our bot. Since we're only checking if the message starts with !ping, other messages such as !ping 123 or !ping abc will also trigger the ping functionality. You can change the behavior to strictly check for !ping only if you want but I'll go with the starts with functionality for now. To reply to the message, we use the .reply() method on message. And believe it or not, it is as simple as that.

You can append the above 2 code snippets below your previous code and we'll be good to go and implement our primary functionality, getting the crypto prices, in the next section.

Commit till this part


The Price Command

Moving onto one of the core functionalities of our: fetching a price of a cryptocurrency with respect to another currency to compare it with. For this, we'll be using the CoinGecko API which is a free API that provides us an endpoint to achieve the functionality we want. Before we move on that, let's once again break our tasks into few subtasks:

  1. Check if the message sent by the user starts with !price
  2. Check if the user has passed 2 arguments along with it: the crypto and the currency to compare with
  3. Get the price from CoinGecko API if the user has passed 2 arguments
  4. Check if we have received a proper response with data from the API. If so, reply to the user's message with the price. If not, reply to the user notifying that there was an error.

To tackle our first subtask, we can use the inbuilt .startsWith() method to check if the message starts with !price. To check if we have 2 arguments, we can split the string and use the spread (...) operator for accumulating the arguments passed by the user in an array.

If the length of the args array is not 2, it means that the user has either passed too few arguments or too many arguments. For fetching the price from the API, we'll use the axios package. You can do this by executing the following command:

npm install axios

Once done, import it into the bot.js by adding this line below the other require statements:

// Require dependencies
const { Client } = require('discord.js');
const dotenv = require('dotenv');
const axios = require('axios'); 	// New line that we added

The API endpoint that we'll be using to fetch the price will be: https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd

The response from this API endpoint looks something like this:

{
  "bitcoin": {
    "usd": 47575
  }
}

This API endpoint takes 2 parameters: ids where we'll be passing the name of the crypto that the user sent in the message and vs_currencies where we'll be passing the currency that we want to compare it against, again taken from the user's message.

Here's the final code for all the functionality we discussed above:

// Reply to !price
  if (message.content.startsWith('!price')) {
    // Get the params
    const [command, ...args] = message.content.split(' ');

    // Check if there are two arguments present
    if (args.length !== 2) {
      return message.reply(
        'You must provide the crypto and the currency to compare with!'
      );
    } else {
      const [coin, vsCurrency] = args;
      try {
        // Get crypto price from coingecko API
        const { data } = await axios.get(
          `https://api.coingecko.com/api/v3/simple/price?ids=${coin}&vs_currencies=${vsCurrency}`
        );

        // Check if data exists
        if (!data[coin][vsCurrency]) throw Error();

        return message.reply(
          `The current price of 1 ${coin} = ${data[coin][vsCurrency]} ${vsCurrency}`
        );
      } catch (err) {
        return message.reply(
          'Please check your inputs. For example: !price bitcoin usd'
        );
      }
    }
  }

You can add this code right below your existing code for the !ping command such that your message event listener function. Here's the GitHub commit with the complete code till this section


The News Command

The next core functionality that we'll be implementing is to get the latest news article related to crypto. As you might have guessed, this is fairly similar to the price command. We'll again be using an API to get the news article. This time, the API will be from News API, which is free but you'll have to create an account in order to generate your API key. So head on over to News API and generate your free API key.

Once you're done with the registration, copy your API key and paste it to the .env file like this:

DISCORD_BOT_TOKEN = ODE2NTk2MTQwOTY2ODA2NTM1.YD9Qaw.uTwkqds9EIapoJ_zJQGX2PAYOWw
NEWS_API_KEY = 6094f663e14952f986c002c636010243

Once again, I would like to remind you not to share these API keys or bot tokens with anyone. I’ve shown my bot token for tutorial purposes and will be discarding it shortly.

Now, let's check out the API endpoint that we'll be using and the response structure.

The endpoint for our purpose will be: https://newsapi.org/v2/everything?q=crypto&apiKey=${process.env.NEWS_API_KEY}&pageSize=1&sortBy=publishedAt

The q parameter stands for the query for which we've passed crypto as the value since we only need those articles that are related to cryptocurrency. We'll also have to send our apiKey as a parameter which we can get from the .env file. The pageSize parameter determines how many articles we'll receive from the API endpoint at a time. Since we need only 1 article, we've set the value to 1. Finally, we can also sort the articles as per rating, publish date, and relevance. Since we need the latest article, we'll set sortBy to publishedAt so that we get the latest article.


  "status": "ok",
  "totalResults": 7503,
  "articles": [
    {
      "source": {
        "id": null,
        "name": "Business Wire"
      },
      "author": null,
      "title": "Cipher Mining Inc., a Newly Formed US-based Bitcoin Mining Company, to Become a Publicly Traded Company via a Merger with Good Works Acquisition Corp.",
      "description": "HOUSTON & NEW YORK--(BUSINESS WIRE)--Cipher Mining Technologies Inc. (“Cipher Mining”), a newly formed U.S.-based Bitcoin mining operation, and Good Works Acquisition Corp. (Nasdaq: GWAC) (“Good Works”), a U.S. publicly-traded special purpose acquisition comp…",
      "url": "https://www.businesswire.com/news/home/20210305005234/en/Cipher-Mining-Inc.-a-Newly-Formed-US-based-Bitcoin-Mining-Company-to-Become-a-Publicly-Traded-Company-via-a-Merger-with-Good-Works-Acquisition-Corp.",
      "urlToImage": "http://www.businesswire.com/images/bwlogo_square.png",
      "publishedAt": "2021-03-05T11:51:19Z",
      "content": "HOUSTON & NEW YORK--(BUSINESS WIRE)--Cipher Mining Technologies Inc. (Cipher Mining), a newly formed U.S.-based Bitcoin mining operation, and Good Works Acquisition Corp. (Nasdaq: GWAC) (Good Wor… [+17142 chars]"
    }
  ]
}

Following the steps from the last command, here's how the code will look like:

// Reply to !news
  if (message.content.startsWith('!news')) {
    try {
      const { data } = await axios.get(
        `https://newsapi.org/v2/everything?q=crypto&apiKey=${process.env.NEWS_API_KEY}&pageSize=1&sortBy=publishedAt`
      );

      // Destructure useful data from response
      const {
        title,
        source: { name },
        description,
        url,
      } = data.articles[0];

      return message.reply(
        `Latest news related to crypto:\n
        Title: ${title}\n
        Description:${description}\n
        Source: ${name}\n
        Link to full article: ${url}`
      );
    } catch (err) {
      return message.reply('There was an error. Please try again later.');
    }
  }

All you need to do now is to append the code below your price feature code. And we've successfully implemented the news feature as well. Yay! If you faced any difficulties, you can cross check your code with this commit


The Help Command

Alright, time to implement one last feature, an easy one too. The !help command can be used by the user to get the list of all the commands that our bots supports and a small description of each command. This is pretty similar to our ping command. All we need to do is to check if the message starts with !help and reply accordingly.

// Reply to !help
  if (message.content.startsWith('!help')) {
    return message.reply(
      `I support 4 commands:\n
      !ping - To check if I am working\n
      !price <coin_name> <compare_currency> - To get the price of a coin with respect to another coin or currency\n
      !news - To get the latest news article related to crypto\n
      !help - For checking out what commands are available`
    );
  }

That is pretty much it. We've built all the features that we desired. You can surely add more features to the bot as per your liking. I would highly encourage you to check out the DiscordJS Documentation to explore more features. What we've built is just scratching the surface of the huge pile of features it provides up.

Nevertheless, I'm sure you've learned something of value and I'm excited to see what you come up with. I hope this was a good introduction to discord.js and I'll be happy to answer all your queries. Here's the final commit of the complete project.


Resources To Check Out

Official DiscordJS Documentation

Create a Discord Bot With Node.js on YouTube

DiscordJS In Depth YouTube Playlist