Create scaleable HTTP Client with Typescript and Fetch API
Muhammad Fuad Ardiono
Posted on June 7, 2023
Using third-party libraries might make development faster or easier. As an engineer, you need to think about how to manage the bundle size of an application, especially if the application is a client side application example case such as React, Vue, Svelte, Angular project and etc. A heavy bundle size will slow down the web page load time.
In this article we will create an HTTP Client using Typescript and Fetch API. The HTTP Client can adjust to the needs of the product to be developed. Real-life example of client side calling API from server A, server B, server C and etc. From each API that is called has a different header such as the header signature requirement. How do we manage it? We create own HTTP client can adapt the product requirement, so we can create HTTPClientAPIA, HTTPClientAPIB, HTTPClientAPIC, and etc.
RESULT & DESIGN
We create an enum HttpClientBaseMethod, we will use that every time we make a request to the API. So what request method code we write, will be safe from typo and more consistent we call POST with value ‘POST’ not ‘post’. We create enum of HttpStatusCode to cast response value status code from Fetch API. We create a standard base header, and standard base status code of our HTTPClient. Why need standard? because we want our code consistent.
export enum HttpClientBaseMehod {
POST = 'POST',
GET = 'GET',
PUT = 'PUT'
}
export interface HttpClientBaseHeader {
'Content-Type': string,
'Authorization'?: string
}
export interface HttpClientBaseStatusCode {
statusCode: number
statusText: HttpStatusCode
}
export enum HttpStatusCode {
Continue = 100,
SwitchingProtocols = 101,
Processing = 102,
Ok = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultiStatus = 207,
AlreadyReported = 208,
ImUsed = 226,
MultipleChoices = 300,
MovedPermanently = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
UseProxy = 305,
SwitchProxy = 306,
TemporaryRedirect = 307,
PermanentRedirect = 308,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
LengthRequired = 411,
PreconditionFailed = 412,
PayloadTooLarge = 413,
UriTooLong = 414,
UnsupportedMediaType = 415,
RangeNotSatisfiable = 416,
ExpectationFailed = 417,
IAmATeapot = 418,
MisdirectedRequest = 421,
UnprocessableEntity = 422,
Locked = 423,
FailedDependency = 424,
UpgradeRequired = 426,
PreconditionRequired = 428,
TooManyRequests = 429,
RequestHeaderFieldsTooLarge = 431,
UnavailableForLegalReasons = 451,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HttpVersionNotSupported = 505,
VariantAlsoNegotiates = 506,
InsufficientStorage = 507,
LoopDetected = 508,
NotExtended = 510,
NetworkAuthenticationRequired = 511,
}
Then we create the class HTTPClientBase and wrapping Fetch API. You can read the documentation of Fetch API here.
Because our main goal is to make an HTTPClient that can be easily scaled, and extend. So we wrap it to the class.
If we don’t wrap the Fetch API with class it will make our code messy, hard to read, hard to scale. Why? Imagine if we just use Fetch API then our source code is big and growing and someday we need refactor the API call from server C to add/change the header or different mechanism to call API that would be a disaster of refactoring. It will take a long time, whereas if we use an agile software methodology it takes a short time in development and release.
`export class HttpClientBase {
private resp: Response
private respBody: any
async createRequest(
url: string,
method: HttpClientBaseMehod,
httpHeader?: HttpClientBaseHeader,
requestData?: any
): Promise<this> {
const requestInit: RequestInit = {
method
}
if (httpHeader) {
requestInit.headers = httpHeader as any
}
if (requestData) {
requestInit.body = JSON.stringify(requestData)
}
this.resp = await fetch(url, requestInit)
this.respBody = await this.resp.json()
return this
}
statusCode(): HttpClientBaseStatusCode {
const statusCode = this.resp.status as HttpStatusCode
const statusText = HttpStatusCode[this.resp.status] as unknown as HttpStatusCode
return {
statusCode,
statusText
}
}
isOk(): boolean {
return this.resp.ok
}
responseByKey<T>(key: string | null = null): T {
if (key) {
return this.respBody[key]
} else {
return this.respBody
}
}
responseData<T>(): T {
return this.respBody.data
}
}`
createRequest is function creating/build the request to Fetch API. We have wrap Fetch API to that function.
statusCode is function casting status code value from Fetch API to our standard HttpClient.
isOk is function wrapping the value condition of the request from Fetch API.
responseByKey is a function that we can use to extract response by key JSON value from the API server.
responseData is a function to return the data by key JSON ‘data’ of response.
We use Generic Type at function responseByKey and responseData because we want our code know what the API server response. We need write the Data Transfer Object (DTO) every response we have get.
Header type can be extend with or (|) type you can extend the type of header just adjust what you need.
IMPLEMENTATION & CASE STUDY
We will implement HTTPClient we have create, with case study of JSONPlaceholder API. Here is code sandbox https://codesandbox.io/s/ts-httpclient-fetch-api-beze7p
REFFERENCE
Http Status Code Gist
https://refactoring.guru/design-patterns/builder
Posted on June 7, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.