This packages relies on
fetch
API
Table of content:
💁♂️ Why?
I just love self-constructive objects. So, why not to create a dynamical self-constructive HTTP client?
🔨 Let's make it work easy
📝 In the beginning...
import { HttpCall } from "@pedro.gilmora\http-call";
const
$api = HttpCall.create({ baseUrl: `https://my-json-server.typicode.com` }),
restPathParamProp = 'posts',
$endpointBase = $api.typicode.demo[ restPathParamProp ];
...
console.log($endpointBase.$path);
And there was light...
https://my-json-server.typicode.com/typicode/demo/posts
🌈 Types...
export interface Post {
id?: number;
title: string;
}
export interface User {
id: number;
userName: string;
}
export interface UserPost extends Post{
user: User
}
⚡ With Callback approach
While we do this...
$endpointBase.get<Post[]>().then(console.log);
We'll get this
Url |
|
Response |
[
{
"id": 1,
"title": "Post 1"
},
{
"id": 2,
"title": "Post 2"
},
{
"id": 3,
"title": "Post 3"
}
] |
🍬 With async
/await
approach and query parameters...
Query parameters objects will be serialized as query string.
const queryParams = { a: 'b', b: 2, c: true, d: new Date(), e: null },
id = 1;
const filtered = await $endpointBase[id].get<Post[]>(queryParams, {
// Tadaa: We have the ability to intercept the request before send it... 👏👏👏
onSend({ url }) {
console.log(url);
}
});
Url |
|
Response |
{
"id": 1,
"title": "Post 1"
} |
💥 Error capture
You can easily handle errors like this (using the same creation context). FetchError
might to the rescue in case you need it
try {
await $api.another.missing.endpoint.post({id:1, title: 'test'});
} catch (error) {
if(error instanceof FetchError && error.code === 404)
await notifyError("We couldn't contact the server", /*timeout*/3500);
}
🔩 BTW... we can transform typed results
Consider to not specify generic-type for result in HTTP verb methods,
transform
hook will help infer types for you by specifying parameters and return types
const user = { id: -1, userName: 'admin' };
const posts: UserPost[] = await $endpointBase.get(undefined,
{
transform(posts: Post[]){
//returns (Post & {user: User})[] which is compatible with UserPost[]
return posts.map(post => Object.assign(post, {user}));
}
}
);
We'll get this
Url |
|
Response |
[
{
"id": 1,
"title": "Post 1",
"user": {
"id": -1,
"name": "admin"
}
},
{
"id": 2,
"title": "Post 2",
"user": {
"id": -1,
"name": "admin"
}
},
{
"id": 3,
"title": "Post 3",
"user": {
"id": -1,
"name": "admin"
}
}
] |
🍬 With async
/await
approach and query parameters...
Query parameters objects will be serialized as query string.
const queryParams = { a: 'b', b: 2, c: true, d: new Date(), e: null },
id = 1;
const filtered = await $endpointBase[id].get<Post[]>(queryParams, {
// Tadaa: We have the ability to intercept the request before send it... 👏👏👏
onSend({ url }) {
console.log(url);
}
});
Url |
|
Response |
{
"id": 1,
"title": "Post 1"
} |
API
HttpCall
Instance
//Caller instance
export type HttpCallerInstance = {
//Retrieve the built-in URL path
$path: string;
//Performs a GET request.
get<T, TOut = T>(
// Record object to be converted to query string
query?: Record<string, any>,
//Custom request configuration
init?: HttpCallInitOf<T,TOut>
): Promise<TOut>;
//Performs a POST request.
post<T, TOut = T>(
//data to be sent
data?: T,
//Custom request configuration
init?: HttpCallInitOf<T, TOut>
): Promise<TOut>;
//Performs a PUT request.
put<T, TOut = T>(
//data to be sent
data?: T,
//Custom request configuration
init?: HttpCallInitOf<T, TOut>
): Promise<TOut>;
//Performs a DELETE request.
delete<T, TOut = T>(
// Record object to be converted to query string
query?: Record<string, any>,
//Custom request configuration
init?: HttpCallInitOf<T,TOut>
): Promise<TOut>;
//configure the current request
withOptions(opts: HttpCallInitOf<T,TOut>): HttpCallerInstance;
//adds query parameters. Useful for POST and PUT requests
withQuery(query: Record<string, any>): HttpCallerInstance;
} & {
// any other specified property access will return the current instance with a new path segment (Proxy)
[x: string|number]: HttpCallerInstance
};