DEV Community

Cover image for How TypeScript Generics Help You Write Less Code
Utkarsh Bhimte
Utkarsh Bhimte

Posted on • Originally published at freecodecamp.org

3

How TypeScript Generics Help You Write Less Code

When developers discuss Typescript, one of their main complaints is that you have to write more code to achieve the same result. While I agree with that, I think Typescript Generics can reduce the code you need to write to a great extent.

What are TypeScript Generics?

TS Generics can be used to bring abstraction into your TS interfaces. Using generics, you can pass an interface as a param to another interface.

Here is an example of a standard API response for both a happy path and an error case.

// successful response ✅
{
    status: 'ok',
    responseCode: 200,
    data: {...}
}

// error response ❌
{
    responseCode: 500,
    errorMessage: "Something went wrong 😅";
}

Instead of writing an interface for every response and adding these keys, you can simply use Generics to create something like this:

interface ApiResponse<T>{
    errorMessage?: string;
    responseCode?: string;
    data?: T;
}
interface UserData {
    name: string;
    email: string;
}

const response: ApiResponse<UserData> = {}

Linking Generics with functions

Let's assume we have a function that we use to make an API request to our backend.

const getRequestTo = (endpoint: string) => {
    return fetch(process.env.BE_HOST + endpoint)
        .then(res => res.json())
}

const userResponse = getRequestTo('/user-data')

The type of userResponse would be any. We can have a better TypeScript implementation here.

const getRequestTo = async <R>(endpoint: string): Promise<ApiResponse<R>> => {
    const request = await fetch(process.env.BE_HOST + endpoint);
    const response = await request.json();
    return response;
};

We can create a similar function for POST requests which also takes JSON as params of type B and the server will send back a JSON response of type R:

const postRequestTo = async <B, R>(
    endpoint: string,
    body: B
): Promise<ApiResponse<R>> => {
    const request = await fetch(process.env.BE_HOST + endpoint, {
        method: "POST",
        body: JSON.stringify(body),
    });

    const response = await request.json();

    return response;
};

Similarly, there can be a PATCH request function as well, which handle‌s partial updates of any entity.

const patchRequestTo = async <B, R>(endpoint: string,body: Partial<B>): Promise<ApiResponse> => {
    const request = await fetch(process.env.BE_HOST + endpoint, {
        method: "PATCH",
        body: JSON.stringify(body)
    });
    const response = await request.json();
    return response;
};

Here is how to implement something like that:

interface RequestBody {}
interface Response {}

const createPost = await postRequestTo<RequestBody, Response>('/post', postData);

const updatePost = await patchRequestTo<RequestBody, Response>('/post', {
    title: "new name"
});

Let's pull it all together with a simple JavaScript class:

class ApiService<T> {
    constructor(entitySlug: string) {
        this.entitySlug = entitySlug;
    }

    private entitySlug: string;

    getOne = async (id: string): Promise<APIResponse<T>> => {
        const request = await fetch(process.env.BE_HOST + this.entitySlug + '/' + id);
        const response = await request.json();
        return response;
    };

    getList = async (): Promise<APIResponse<T[]>> => {
        const request = await fetch(process.env.BE_HOST + this.entitySlug);
        const response = await request.json();
        return response;
    };

    create = async (body: Omit<T, 'id' | 'created' | 'updated'>): Promise<APIResponse<T>> => {
        const request = await fetch(process.env.BE_HOST + this.entitySlug, {
            method: 'POST',
            body: JSON.stringify(body)
        });

        const response = await request.json();
        return response;
    };

    update = async (
        body: Omit<Partial<T>, 'id' | 'created' | 'updated'>
    ): Promise<APIResponse<T>> => {
        const request = await fetch(process.env.BE_HOST + this.entitySlug + '/' + body.id, {
            method: 'PATCH',
            body: JSON.stringify(body)
        });
        const response = await request.json();
        return response;
    };

    delete = async (id: string): Promise<void> => {
        const request = await fetch(process.env.BE_HOST + this.entitySlug + '/' + id, {
            method: 'DELETE'
        });
        const response = await request.json();
        return response;
    };
};

and you can then create an entity service like this:

export const postService = new ApiService<Post>('/post');

and it's all linked and ready for you.

VS Code auto-suggesting according to the typescript configuration that we have implemented

Typescript has the potential to increase the developer experience tenfold if appropriately configured. These are some strategies to make that configuration process more comfortable. I hope this will help you use Typescript better in your current codebase.

Playwright CLI Flags Tutorial

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed
  • 2:34 --only-changed
  • 4:27 --repeat-each
  • 5:15 --forbid-only
  • 5:51 --ui --headed --workers 1

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

Watch Full Video 📹️

Top comments (0)

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay