Amblem
Furkan Baytekin

Why Use Repository Classes?

Organize Your Code with Repositories

Why Use Repository Classes?
151
4 minutes

Why You Should Use Repository Classes for Data Management

When you’re building software, keeping your code simple and easy to work with is super important. One way to do that is by using repository classes. These classes help you manage data and keep your code neat. Think of them as a middleman between your app and the data source, like a database or an API.

Let’s talk about why using repository classes is a great idea. We’ll use a ProductRepository example to show how they can help when managing product data in an online store. By the end, you’ll see how they make coding easier and more efficient.


What Happens Without a Repository?

Imagine you’re working on an online store. To get product info, you might write code like this:

ts
// Directly fetching data in a service async () => { const product = await database.query(`SELECT * FROM products WHERE id = $1`, [productId]) const productList = await database.query(`SELECT * FROM products WHERE category_id = $1`, [categoryId]) }

This works fine for small projects, but it has problems:

  1. Repeated Code: You’ll find yourself writing the same database queries in different places.
  2. Hard to Change: If you switch to a new database, you’ll need to update your code everywhere.
  3. Difficult to Test: Testing this code means dealing with actual database queries.
  4. Messy Code: Your business logic and database code get all tangled up.

How Repository Classes Solve These Problems

Repository classes organize your data-related code in one place. They give you an easy way to interact with your data without worrying about the details.

Example: Creating a ProductRepository

Here’s how you can move the data logic into a repository:

ts
class ProductRepository { private readonly database: Database public constructor(database: Database) { this.database = database } public async getProduct(productId: number): Product { return await this.database.query(`SELECT * FROM products WHERE id = $1`, [productId]) } public async getProductList(categoryId: number): Paginated<Product> { return await this.database.query(`SELECT * FROM products WHERE category_id = $1`, [categoryId]) } public async searchProducts(keyword: string): Paginated<Product> { return await this.database.query( `SELECT * FROM products WHERE name ILIKE $1`, [`%${keyword}%`] ) } }

Using the Repository Class

ts
const database = new DatabaseClient() // Your database connection const productRepository = new ProductRepository(database) // Get a single product const product = await productRepository.getProduct(1) // Get products in a category const products = await productRepository.getProductList(10) // Search for products by name const searchResults = await productRepository.searchProducts("laptop")

Why Use Repository Classes?

1. Keep Things Organized

By putting all product-related queries in ProductRepository, you don’t have to worry about messy database details in other parts of your app. Plus, using techniques like dependency injection (where you pass the database client into the repository), your code becomes flexible and easier to test.

2. Easier Testing

You can mock the database when testing the repository:

ts
const mockDatabase = { query: jest.fn() } const productRepository = new ProductRepository(mockDatabase) mockDatabase.query.mockResolvedValue([{ id: 1, name: "Mock Product" }]) const products = await productRepository.getProductList(10) expect(products).toEqual([{ id: 1, name: "Mock Product" }]) expect(mockDatabase.query).toHaveBeenCalledWith( `SELECT * FROM products WHERE category_id = $1`, [10] )

3. Reuse Code

Once you create the repository, you can use it in different parts of your app, like services, controllers, or background jobs. This prevents duplicate code.

4. Easier Updates

If you change your database, you only need to update the repository. Everything else stays the same.

5. Focus on Business Logic

With the data-handling code in the repository, you can focus on solving business problems in your services and controllers.


Extra Features with Repositories

Repository classes can handle more than just databases. For example:

Example: Adding Caching

Here’s how you can extend ProductRepository to include caching:

ts
class ProductRepository { private readonly database: Database private cache: Record<Property key, unknown> public constructor(database: Database, cache: Record<Property key, unknown>) { this.database = database this.cache = cache } public async getProduct(productId: number) { const cacheKey = `product:${productId}` const cachedProduct = await this.cache.get(cacheKey) if (cachedProduct) return JSON.parse(cachedProduct) const product = await this.database.query(`SELECT * FROM products WHERE id = $1`, [productId]) await this.cache.set(cacheKey, JSON.stringify(product)) return product } }

Wrap-Up

Using repository classes makes your code cleaner, easier to test, and simpler to maintain. They help you keep your data logic separate, reducing duplicate code and making your app more flexible. Whether you’re building a small app or a big system, repositories are a smart choice.

Try using repository classes in your next project—you’ll thank yourself later!

Suggested Blog Posts