Advanced Typescript by Example: API Service Manager

Implementing a generic API service manager with Typescript in React

What is an API service manager?

const api = 
new APIService()
.headers(...)
.method('POST')
.somethingElse();
^
chaining methods in 1 expression

Setting up the project

# generate new CRA typescript project
yarn create react-app api-service --typescript
# jump into the folder and install dependencies
cd api-service
yarn
# start server
yarn start
# clone the project
git clone https://github.com/rossbulat/ts-api-service-manager.git
# jump into the folder and install dependencies
cd ts-api-service-manager
yarn
# start server
yarn start

APIService Class

Automatic constructor parameter assignment

// APIService.tsexport class APIService {

constructor (private _authToken: string) {
}
}
// the alternative to ACPA: lots of repetitionexport class APIService {
private _authToken: string;
constructor (_authToken: string) {
this._authToken = _authToken;
}
}
// App.tsx...
import { APIService} from './APIService';
const App: React.FC = () => { const api = new APIService('my-super-secret-auth-token');
...
}

Getters and setters

// getterget authToken (): string {
return this._authToken;
}
// setterset authToken (newAuthToken: string) {
this._authToken = newAuthToken;
}
const api = new APIService('my-super-secret-auth-token');// calling the setter
api.authToken = 'new-secret-authtoken';
// calling the getter
if (api.authToken) {
console.log('remember to remove this before building');
console.log(api.authToken);
}

Configuring Request Headers

Generic Key Value Type

'Accept' => 'application/json'
// candidate for a header typeexport type ApiHeader = {
key: string;
value: string;
};
// types.tsexport type KeyValue<T, U> = {
key: T,
value: U,
};
public setHeaders (headers: KeyValue<string, string>[]): APIService {
return this;
}
// App.tsx const api = new APIService('my-super-secret-auth-token')
.setHeaders([
{
key: 'Accept',
value: 'application/json'
},
{
key: 'Content-Type',
value: 'application/json'
},
]);

Fluent interface

// every chained method must return `this`const obj = new Obj()
.setSomething() < returns this
.setSomethingElse() < returns this
.setAnotherThing(); < returns this
// _method implementation summaryexport type ApiMethod = "POST" | "GET";private _method: ApiMethod = "POST";public setMethod (newMethod: ApiMethod): APIService {
this._method = newMethod;
return this;
}

Setting headers as string[ ][ ]

// APIService.tsprivate _headers: string[][] = [];public setHeaders (headers: KeyValue<string, string>[]): APIService {
for (const i in headers) {
if (headers[i].hasOwnProperty('key')
&& headers[i].hasOwnProperty('value')) {

this._headers.push([
headers[i].key,
headers[i].value
]);

}
}
return this;
}

Other methods for _headers

get headers (): string[][] {
return this._headers;
}
public resetHeaders (): void {
this._headers = [];
}

The RequestBody class

// APIService.tsexport class RequestBody<T> {   constructor (private _requestBody: T) {
}
get requestBody (): T {
return this._requestBody;
}
set requestBody (newRequestBody: T) {
this._requestBody = newRequestBody;
}
}
type ArticleCategory = "Typescript" | "React" | "Rust";type ApiRequestAuthor = {
author: string,
category: ArticleCategory,
};
const body = new RequestBody<ApiRequestAuthor>({
author: 'ross',
category: "Typescript",
});

Formatting and Calling fetch()

fetch('/endpoint/here', 
{
headers: {
'Accept': 'application/json',
...
},
method: "POST",
body: JSON.stringify({
...
})
}
)
.then(res => res.json())
.then(data => {
// handle response
})
.catch(() => {
// handle request error
});
// APIService.tspublic request<T> (body: T): RequestInit {
return {
headers: this._headers,
method: this._method,
body: JSON.stringify(body),
}
}
// App.tsxfetch('/endpoint/here', api.request(body))
.then(res => res.json())
.then(data => {
// handle response
})
.catch(() => {
// handle request error
});

Handling the response

{
Result: "success" | "failure",
Response: {
// this can either be what we requested, or an error message
}
}
export type ApiResult = "success" | "failure";export type ApiError = {
ErrorCode: string,
Description: string,
};
export type ApiResponse<T> = {
Result: ApiResult,
Response: T | ApiError,
};
// App.tsxfetch('/endpoint/here', api.request(body))
.then(res => res.json())
.then(data => {
const response: ApiResponse<ArticleResponse> = data;

// do something with `response`

})
.catch(() => {
// handle request error
});

Summary

Programmer and Author. Director @ JKRBInvestments.com. Creator of LearnChineseGrammar.com for iOS and Android.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store