Featured resource
Tech Upskilling Playbook 2025
Tech Upskilling Playbook

Build future-ready tech teams and hit key business milestones with seven proven plays from industry leaders.

Learn more
  • Labs icon Lab
  • Core Tech
Labs

Guided: Build Reusable Components with TypeScript Generics

Learn to use TypeScript generics to build a reusable, type-safe data layer for a RESTful API. In this lab, you'll implement a generic repository pattern to handle multiple entity types with minimal code duplication, then expose these operations via Express endpoints. By the end, you'll understand how generics and type constraints enable scalable, maintainable backend design.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 33m
Last updated
Clock icon Aug 08, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Generic Syntax

    Understanding TypeScript's Generic Syntax

    Generics are a powerful feature in TypeScript that allows you to write flexible, reusable code without losing type safety. In this section, you'll learn how to define and use generics effectively.

    Declaring Generics

    Generics are declared using angle brackets, such as <T>, where T is a placeholder for a type. You can use generics with functions, classes, or interfaces to make them work with any data type.

    Example: Generic Function

    Here's a simple example of a generic function:

    function identity<T>(arg: T): T {
        return arg;
    }
    
    // explicitly specifying the type
    const result1 = identity<string>("hello");
    // or let TypeScript infer it
    const result2 = identity(42);
    
    • identity<T> declares a generic function with a type parameter T.
    • When calling identity, you can specify the type explicitly for example, identity<string>("hello").
    • Alternatively, you can let TypeScript infer the type from the argument.

    With this approach, you can write functions that support any type. TypeScript maintains type safety and automatically infers types for you.


    Next, you'll use these concepts to review your repository patterns and plan how to introduce generics for greater flexibility and maintainability.

  2. Challenge

    Why use Generics?

    Why Use Generics?

    Generics in TypeScript allow you to write reusable, type-safe code that works with any data type. By abstracting over types, you eliminate duplicated logic, simplify maintenance, and ensure consistency—all while preserving strong compile-time checks.

    The Problem

    To see the problem, consider the following two repository classes: CustomerRepository.ts and ProductRepository.ts. Both classes implement similar CRUD operations but handle different data types.

    Note: You can view and edit these files in the /repositories directory located in the file explorer on the right.

    When you duplicate logic across files, it increases the chance of inconsistency, makes maintenance harder, and raises the risk of errors—especially when you need to make updates in multiple places.

    The Solution: Generics

    Generics allow you to create a single implementation that works with any type. By introducing a generic type parameter, you eliminate duplication and improve type safety because TypeScript catches type errors at compile time.


    Next, apply these concepts to improve your repository patterns by introducing generics for better flexibility and maintainability.

  3. Challenge

    Refactoring IRepository

    Refactoring IRepository for Stronger Typing

    To make your repositories more flexible and type-safe, define a generic interface that can handle any entity type. This allows repository classes to share common CRUD operations without losing the benefits of TypeScript’s strong typing.

    Currently, both CustomerRepository and ProductRepository implement the same set of CRUD methods, which are declared in the IRepository.ts interface. However, this interface uses the any type for its operations, which undermines type safety.

    When you refactor the interface to use generics, you ensure that each repository works only with its specific entity type—reducing errors and improving code quality.

    Next you'll create the shared functionality in a BaseRepository.ts file that the repository files can extend.

  4. Challenge

    Designing a Reusable BaseRepository

    Designing a Reusable BaseRepository

    After defining a generic repository interface, you'll implement a base class that provides type-safe CRUD operations for any entity type. This reusable class becomes the foundation of your repositories, allowing you to centralize common logic and reduce code duplication.

    You can then create specific repositories by extending this base class for each entity type. Once you’ve declared the class, implement the CRUD methods.

    You can reuse the logic from an existing repository (for example, CustomerRepository or ProductRepository) and update it to work with generics.

  5. Challenge

    Restricting Generic Types

    Restricting Generic Types

    When you defined the BaseRepository class, you used a generic type T. However, TypeScript could not guarantee that T included an id property. To fix this, you should restrict T so it must extend a base entity type that defines the id property.

    This change ensures that any type used with BaseRepository will always have the required structure.

    There are two common ways to enforce this constrain.

    Using a Base Entity Class:
    • Create a base class (such as BaseEntity) with the id property, and require that T extends this class:
    BaseRepository<T extends BaseEntity>
    
    Using a Generic Constraint:
    • Directly constrain T to types that have an id property:
    BaseRepository<T extends { id: number }>
    

    In the /models directory, you'll find a BaseEntity.ts file. Each entity (such as Customer) already implements BaseEntity, so they all include the required id property.

    Now that BaseRepository enforces the BaseEntity constraint, you can confidently create specialized repositories for your entities. This approach guarantees type safety and consistency throughout your application.

  6. Challenge

    Refactoring to Generic Type

    Refactoring to Generic Type

    Now you can simplify your repositories by having them extend the generic BaseRepository class. This approach will eliminate duplicated code while preserving any specific functionality unique to each repository.

    Congratulations! You have successfully refactored your repositories to use a generic base class. This approach not only reduces code duplication but also enhances maintainability and readability of your codebase.

Jeff Hopper is a polyglot solution developer with over 20 years of experience across several business domains. He has enjoyed many of those years focusing on the .Net stack.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.