- Lab
- Core Tech

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.

Path Info
Table of Contents
-
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>
, whereT
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 parameterT
.- 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.
-
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
andProductRepository.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.
-
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
andProductRepository
implement the same set of CRUD methods, which are declared in theIRepository.ts
interface. However, this interface uses theany
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. -
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
orProductRepository
) and update it to work with generics. -
Challenge
Restricting Generic Types
Restricting Generic Types
When you defined the
BaseRepository
class, you used a generic typeT
. However, TypeScript could not guarantee thatT
included anid
property. To fix this, you should restrictT
so it must extend a base entity type that defines theid
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 theid
property, and require thatT
extends this class:
BaseRepository<T extends BaseEntity>
Using a Generic Constraint:
- Directly constrain
T
to types that have anid
property:
BaseRepository<T extends { id: number }>
In the
/models
directory, you'll find aBaseEntity.ts
file. Each entity (such asCustomer
) already implementsBaseEntity
, so they all include the requiredid
property.Now that
BaseRepository
enforces theBaseEntity
constraint, you can confidently create specialized repositories for your entities. This approach guarantees type safety and consistency throughout your application. - Create a base class (such as
-
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.
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.