The generic repository pattern helps you manage data in a simple, reusable way. It lets you write one set of code to handle many types of data, so you don't repeat yourself.
Generic repository pattern in Typescript
interface IRepository<T> {
getAll(): Promise<T[]>;
getById(id: string): Promise<T | null>;
create(item: T & { id: string }): Promise<T>;
update(id: string, item: T & { id: string }): Promise<T | null>;
delete(id: string): Promise<boolean>;
}
class GenericRepository<T> implements IRepository<T> {
private items: Map<string, T> = new Map();
async getAll(): Promise<T[]> {
return Array.from(this.items.values());
}
async getById(id: string): Promise<T | null> {
return this.items.get(id) ?? null;
}
async create(item: T & { id: string }): Promise<T> {
this.items.set(item.id, item);
return item;
}
async update(id: string, item: T & { id: string }): Promise<T | null> {
if (!this.items.has(id)) return null;
this.items.set(id, item);
return item;
}
async delete(id: string): Promise<boolean> {
return this.items.delete(id);
}
}The <T> means this code works with any type you give it.
Methods return Promise to simulate async data access like databases.
interface User {
id: string;
name: string;
email: string;
}
const userRepo = new GenericRepository<User>();await userRepo.create({ id: '1', name: 'Alice', email: 'alice@example.com' }); const users = await userRepo.getAll();
interface Product {
id: string;
title: string;
price: number;
}
const productRepo = new GenericRepository<Product>();This program shows how to use the generic repository with a User type. It adds users, reads them, updates one, deletes one, and shows the results at each step.
interface IRepository<T> {
getAll(): Promise<T[]>;
getById(id: string): Promise<T | null>;
create(item: T & { id: string }): Promise<T>;
update(id: string, item: T & { id: string }): Promise<T | null>;
delete(id: string): Promise<boolean>;
}
class GenericRepository<T> implements IRepository<T> {
private items: Map<string, T> = new Map();
async getAll(): Promise<T[]> {
return Array.from(this.items.values());
}
async getById(id: string): Promise<T | null> {
return this.items.get(id) ?? null;
}
async create(item: T & { id: string }): Promise<T> {
this.items.set(item.id, item);
return item;
}
async update(id: string, item: T & { id: string }): Promise<T | null> {
if (!this.items.has(id)) return null;
this.items.set(id, item);
return item;
}
async delete(id: string): Promise<boolean> {
return this.items.delete(id);
}
}
interface User {
id: string;
name: string;
email: string;
}
async function main() {
const userRepo = new GenericRepository<User>();
await userRepo.create({ id: '1', name: 'Alice', email: 'alice@example.com' });
await userRepo.create({ id: '2', name: 'Bob', email: 'bob@example.com' });
const allUsers = await userRepo.getAll();
console.log('All users:', allUsers);
const user1 = await userRepo.getById('1');
console.log('User with id 1:', user1);
await userRepo.update('2', { id: '2', name: 'Bobby', email: 'bobby@example.com' });
const updatedUser = await userRepo.getById('2');
console.log('Updated user with id 2:', updatedUser);
const deleted = await userRepo.delete('1');
console.log('Deleted user with id 1:', deleted);
const usersAfterDelete = await userRepo.getAll();
console.log('Users after delete:', usersAfterDelete);
}
main();Make sure each item has a unique id property for this pattern to work well.
This pattern helps keep your code clean and easy to change later.
The generic repository pattern lets you write one data handler for many types.
It helps keep your code organized and reusable.
You can add, get, update, and delete items easily with this pattern.
Practice
What is the main benefit of using the Generic Repository Pattern in TypeScript?
Solution
Step 1: Understand the purpose of the generic repository
The generic repository pattern is designed to handle data operations for many types using one class or interface.Step 2: Compare options with the pattern's purpose
Only It allows handling multiple data types with one reusable class. correctly describes this benefit. Options A, B, and C are unrelated to the pattern's main use.Final Answer:
It allows handling multiple data types with one reusable class. -> Option AQuick Check:
Generic repository = reusable data handler [OK]
- Thinking it generates UI components
- Confusing it with TypeScript compilation
- Believing it removes interfaces
Which of the following is the correct way to declare a generic repository interface in TypeScript?
interface IRepository<T> {
add(item: T): void;
get(id: number): T | undefined;
}Solution
Step 1: Check generic syntax correctness
interface IRepository<T> { add(item: T): void; get(id: number): T | undefined; } correctly declares a generic interface with type parameter <T> and uses it consistently in method signatures.Step 2: Validate method signatures
interface IRepository<T> { add(item: T): void; get(id: number): T | undefined; }'s methods useitem: Tand returnT | undefinedfor get, which is correct. Other options misuse types or parameter types.Final Answer:
interface IRepository<T> { add(item: T): void; get(id: number): T | undefined; } -> Option AQuick Check:
Generic interface syntax = interface IRepository<T> { add(item: T): void; get(id: number): T | undefined; } [OK]
- Using 'any' instead of generic type T
- Mismatching parameter and return types
- Using wrong id parameter type
Given the following generic repository class, what will be the output of the console.log statement?
class Repository<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getAll(): T[] {
return this.items;
}
}
const repo = new Repository<string>();
repo.add('apple');
repo.add('banana');
console.log(repo.getAll());Solution
Step 1: Understand the generic repository usage
The repository is created for strings, so it stores string items in an array.Step 2: Trace the add and getAll methods
Two strings 'apple' and 'banana' are added, so getAll returns ['apple', 'banana'].Final Answer:
['apple', 'banana'] -> Option DQuick Check:
Added items = ['apple', 'banana'] [OK]
- Expecting only one item in output
- Thinking getAll returns empty array
- Confusing type errors
Identify the error in this generic repository implementation:
class Repository<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(id: number): T {
return this.items[id];
}
}
const repo = new Repository<number>();
console.log(repo.get(0));Solution
Step 1: Analyze the get method return type
The get method returnsthis.items[id], which may be undefined if the index is invalid.Step 2: Check return type compatibility
Sinceundefinedis possible, the return type should beT | undefinedto avoid runtime errors.Final Answer:
The get method should return T | undefined to handle missing items. -> Option CQuick Check:
Return type must allow undefined for missing index [OK]
- Ignoring possible undefined return
- Changing add method unnecessarily
- Making items public without reason
You want to create a generic repository that supports updating an item by id. Which method signature correctly fits this requirement?
Solution
Step 1: Understand update method needs
To update an item, you need to specify which item by id and provide the new item data.Step 2: Evaluate method signatures
update(id: number, item: T): boolean takes an id and a new item of type T, returning boolean to indicate success, which fits the requirement best.Final Answer:
update(id: number, item: T): boolean -> Option BQuick Check:
Update needs id and new item, returns success [OK]
- Missing id parameter
- Using wrong parameter types
- Returning wrong type from update
Ready to prove it?
Pass the test and this pattern moves unknown
