DEV Community

yossarian
yossarian

Posted on • Edited on • Originally published at catchts.com

1

Using fetch in TypeScript with typestate pattern

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)

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay