DEV Community

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 (0)

pic
Editor guide