Create An API Wrapper with Node.js

05/11/22

Intro

A few years back I was tasked with creating a wrapper library for a rather large, and clunky API. The wrapper needed to be user friendly, and had to hide any confusing details from the user. Today I wanted to revist this and show you just how simple it is to wrap an API with Node.js.

The OpenDota API

Dota 2 is a multiplayer online battle arena video game developed and published by Valve. They also provide an API for it that has a lot of features. By no means is their API bad or confusing, but it will still be a good example on how you could wrap any API.

Creating A Base Class

In a folder called lib, I will create an index.js file and setup the basics of the class.

const axios = require('axios');

module.exports = class OpenDota {
  constructor(apiKey) {
    this.baseUrl = 'https://api.opendota.com/api';
    axios.defaults.headers.common['Authorization'] = `Bearer ${apiKey}`;
  }
};

Nothing crazy going on here. We have the api's base url hardcoded into the class, so it is hidden from the user. Also, we make sure to set the API key so that it is sent on every request created by axios.

If you are a little confused, I will clear this up by creating another index.js file outside of the lib folder, and add the following:

const OpenDota = require('./lib/index');
const API_KEY = 'edd6xf36-38cb-4315-89ea-96a519210e96';

const dota = new OpenDota(API_KEY);

Adding Methods To Our API Wrapper

Then OpenDota API has a good amount of features, so today I am going to focus on four endpoints:

  • Retrieving a list of professional matches.
  • Retrieving information about a specific match.
  • Retrieving a list of professional players.
  • Retrieving information about a specific player.

You'll probably be surprised at just how little code we need to write, so I am going to add four methods onto the class all at once.

const axios = require('axios');

module.exports = class OpenDota {
  constructor(apiKey) {
    this.baseUrl = 'https://api.opendota.com/api';
    axios.defaults.headers.common['Authorization'] = `Bearer ${apiKey}`;
  }

  async proMatches() {
    const { data } = await axios.get(`${this.baseUrl}/proMatches`);
    return data;
  }

  async match(matchId) {
    const { data } = await axios.get(`${this.baseUrl}/matches/${matchId}`);
    return data;
  }

  async proPlayers() {
    const { data } = await axios.get(`${this.baseUrl}/proPlayers`);
    return data;
  }

  async player(accountId) {
    const { data } = await axios.get(`${this.baseUrl}/players/${accountId}`);
    return data;
  }
};

For each endpoint, we create a method on the class that handles making a specific network request. We also make sure to destructure data from the axios response, so that part is hidden from the user.

Using Our API Wrapper

The last step is to actually use our API wrapper! Just so it's clear, dota.proMatches and dota.proPlayers will both return an array of objects.

const OpenDota = require('./lib/index');
const API_KEY = 'edd6xf36-38cb-4315-89ea-96a519210e96';

const dota = new OpenDota(API_KEY);

(async () => {
  const [firstMatch] = await dota.proMatches();
  const match = await dota.match(firstMatch.match_id);
  
  const [firstPlayer] = await dota.proPlayers();
  const player = await dota.player(firstPlayer.account_id);
})();

Summary

That's about it for our API wrapper folks. Of course this was a very simple example, but I believe following this approach could be scaled out well. For a real wrapper, you'd probably want to separate functionality into different classes, instead of having everything on one class like we did here. Anyways, I hope this reaches out and helps someone.

As always, if you have any problems understanding any of this, feel free to shoot me a message, and I'll make sure it's crystal clear.

Want To Level Up Your JavaScript Game?

Book a private session with me, and you will be writing slick JavaScript code in no time.