loading...

Using fetch in TypeScript with typestate pattern

captainyossarian profile image yossarian ・2 min read

If You want to write generic class for server requests this post is for You.

I preffer code examples over the words and funny images, so You will not spend a lot of time here.

Let's define our allowed endpoints and constraints:

const enum Endpoints {
    users = '/api/users', // GET | POST       
    notes = '/api/notes', //  POST | DELETE   
    entitlements = '/api/entitlements' // GET 
}
Enter fullscreen mode Exit fullscreen mode

Let's assume that backend developer allowed You to make:

  • GET | POST requests for users
  • POST | DELETE requests for notes
  • GET requests for entitlements

Now, we can also define allowed methods for each endpoint :


interface HandleUsers {
    get<T>(url: Endpoints.users): Promise<T>;
    post(url: Endpoints.users): Promise<Response>;
}

interface HandleNotes {
    post(url: Endpoints.notes): Promise<Response>;
    delete(url: Endpoints.notes): Promise<Response>;
}

interface HandleEntitlements {
    get<T>(url: Endpoints.entitlements): Promise<T>;
}
Enter fullscreen mode Exit fullscreen mode

Now, we can define our main class:

class Api {
    get = <T = void>(url: Endpoints): Promise<T> => fetch(url)
    post = (url: Endpoints) => fetch(url, { method: 'POST' })
    delete = (url: Endpoints) => fetch(url, { method: 'DELETE' })
}
Enter fullscreen mode Exit fullscreen mode

For now, class Api does not have any constraints.
So let's define them:


// Just helper
type RequiredGeneric<T> = T extends void
    ? { __TYPE__ERROR__: 'Please provide generic parameter' }
    : T

interface HandleHttp {
    <T extends void>(): RequiredGeneric<T>
    <T extends Endpoints.users>(): HandleUsers;
    <T extends Endpoints.notes>(): HandleNotes;
    <T extends Endpoints.entitlements>(): HandleEntitlements;
}
Enter fullscreen mode Exit fullscreen mode

As You see, HandleHttp is just overloading for function. Nothing special except the first line. I will come back to it later.

We have class Api and overloadings for function. How we can combine them? Very simple - we will just create a function which returns instance of Api class.

const handleHttp: HandleHttp = <_ extends Endpoints>() => new Api();
Enter fullscreen mode Exit fullscreen mode

Take a look on generic parameter of httpHandler and HandleHttp interface, there is a relation between them.

Let's test our result:

const request1 = handleHttp<Endpoints.notes>() // only delete and post methods are allowed
const request2 = handleHttp<Endpoints.users>() // only get and post methods are allowed
const request3 = handleHttp<Endpoints.entitlements>() // only get method is allowed
Enter fullscreen mode Exit fullscreen mode

Wait, what if I forgot to set generic parameter for handleHttp?
Trust me, this is not a problem :) Just hover the mouse on request. Now You, why I used RequiredGeneric

const request = handleHttp() // 'Please provide generic parameter'
Enter fullscreen mode Exit fullscreen mode

You will not able to call any method without generic parameter.
P.S. I believe I used here typestate pattern.

It is the end folks)

Discussion

pic
Editor guide