A Simple Typescript Class To Make HTTP Calls Using Axios
I love convenience classes. I’ve built hundreds of them, but this is one I keep in my “toolbox” the most because as web developers we make a lot of HTTP calls. While this example uses the Axios npm package, you could effectively swap it out with your favorite HTTP client.
By the end of this tutorial, we’ll have constructed a simple Typescript class that will handle all the logic for marshaling data over HTTP.
Prerequisites
Before we begin, we need to import Axios to our npm package dependencies.
npm i --save axios
As this is Typescript, let’s also make sure that we import the types.
npm i --save-dev @types/axios @types/node
Constructing the class: GET
To keep things simple, let’s first implement GET:
import axios, { AxiosResponse } from 'axios';
export default class HttpService { async get(baseURL: string): Promise<AxiosResponse> {
return axios.get(baseURL);
}}
This is pretty bare-bones, so let’s add in some additional parameters that we know we may need to provide for a GET request. Let’s provision for some optional parameters including an endpoint, a map of request parameters, and a map of headers.
import axios, { AxiosResponse } from 'axios';export default class HttpService { async get(baseURL: string,
endpoint?: string,
params?: { [ key: string ]: any },
headers?: { [ key: string ]: any })
: Promise<AxiosResponse>
{
const url = endpoint ? baseURL.concat(endpoint) : baseURL;
const options = { params, headers }; return axios.get(url, options);
}}
Essentially, we’re are using this function to assemble the parameters necessary to feed into Axios. Because Axios returns the response as a Promise, we could await
the response, or put the response in a .then().catch()
clause.
Building the POST method
For our POST call, we can start by copying the pattern but now adding in room for a body, as well as a flag to determine if the post message that we’re sending is intended to be sent as form input.
import axios, { AxiosResponse } from 'axios';
import { URLSearchParams } from 'url';export default class HttpService { async get( ... ) { ... } async post(baseURL: string,
endpoint?: string,
body?: any,
params?: { [ key: string ]: any },
headers?: { [ key: string ]: any },
asFormEncoded?: boolean)
: Promise<AxiosResponse>
{
const url = endpoint ? baseURL.concat(endpoint) : baseURL;
const options = { params, headers };
if (asFormEncoded && body) {
const bodyParams = new URLSearchParams();
for (const b of Object.keys(body)) {
bodyParams.append(b, body[ b ]);
}
body = bodyParams;
} return axios.post(url, body, options);
}}
Note: The URLSearchParams comes from the @types/node
type package, we import it as demonstrated.
The if-statement is responsible for properly encoding the post body message so that it can be sent as a form-encoded string. For example, if we pass in a body of:
{ a: 1, b: 'two' }
We could expect the parsed string to be:
a=1&b=two
Depending on your project, you could scrap this part altogether, but I run across these enough for me to keep it around for now.
Handling multiple GET’s in parallel
We can take this a step further and implement a method that will make many GET requests simultaneously, using Axios’ all method.
For this example, let’s envision an API we have to hit multiple times, but with different parameters on each hit (for a paginated request, say). In that case we can do the following:
import axios, { AxiosResponse } from 'axios';
import { URLSearchParams } from 'url';export default class HttpService { async get( ... ) { ... } async post( ... ) { ... } async getMany(baseURL: string,
endpoint?: string,
paramMaps?: { [ key: string ]: any }[],
headers?: { [ key: string ]: any })
: Promise<AxiosResponse[]>
{
const tasks: any[] = [];
for (const params of paramMaps || []) {
tasks.push(this.get(baseURL, endpoint, params, headers));
}
return axios.all(tasks).then(responses => responses.map(resp => resp.data.data || []));
}
}
Note how we’re passing in a list of parameter maps, one for each request. This particular request is a bit different than the GET command in that it handles the promise in-house, but this is optional. You would just need to handle the list of responses down the road.
This class is simple, it may not look like much, but you’ve reduced the amount of code needed to make HTTP calls down to a simple function call. We can of course extend this to support PUT, DELETE, etc, but I’ll leave that part up to you.
Here is the entire class we just made for your copy-and-paste pleasure:
import axios, { AxiosResponse } from 'axios';
import { URLSearchParams } from 'url';export default class HttpService { async get(baseURL: string,
endpoint?: string,
params?: { [ key: string ]: any },
headers?: { [ key: string ]: any })
: Promise<AxiosResponse>
{
const url = endpoint ? baseURL.concat(endpoint) : baseURL;
const options = { params, headers }; return axios.get(url, options);
} async post(baseURL: string,
endpoint?: string,
body?: any,
params?: { [ key: string ]: any },
headers?: { [ key: string ]: any },
asFormEncoded?: boolean)
: Promise<AxiosResponse>
{
const url = endpoint ? baseURL.concat(endpoint) : baseURL;
const options = { params, headers };
if (asFormEncoded && body) {
const bodyParams = new URLSearchParams();
for (const b of Object.keys(body)) {
bodyParams.append(b, body[ b ]);
}
body = bodyParams;
} return axios.post(url, body, options);
}async getMany(baseURL: string,
endpoint?: string,
paramMaps?: { [ key: string ]: any }[],
headers?: { [ key: string ]: any })
: Promise<AxiosResponse[]>
{
const tasks: any[] = [];
for (const params of paramMaps || []) {
tasks.push(this.get(baseURL, endpoint, params, headers));
}
return axios.all(tasks).then(responses => responses.map(resp => resp.data.data || []));
}
}
Happy Hacking!