Rules Hub

Coding Rules Library

← Back to all rules
backend ruleStack: node
architecturerepository-patternclean-codeseparation-of-concernsapi-design

Encapsulate specific query and mapping chains in repositories

Avoid manually chaining query filters and mapping logic inside controllers; extract them into specific repository methods.

PR: Feat/FCK-1888 - Create new articles by tag endpoint #1291Created: Dec 8, 2025

Bad example

Old codets
1// src/controllers/ArticleController.ts
2export const getTagTeasers = async (req: Request, res: Response) => {
3 const { tagName } = req.params;
4
5 // BAD: Query chain and mapping logic leaked into controller
6 const articles = await ArticleRepository
7 .byTagName(tagName)
8 .filterByPaid(false)
9 .limit(10)
10 .execute();
11
12 const teasers = articles.map(article => ({
13 title: article.title,
14 url: article.url,
15 image: article.leadImage?.url
16 }));
17
18 res.json(teasers);
19};

Explanation (EN)

The controller knows too much about how to fetch and transform data. It manually chains filter methods and maps the result to a DTO, making this logic hard to reuse and test in isolation.

Objašnjenje (HR)

Kontroler zna previše o tome kako dohvatiti i transformirati podatke. Ručno ulančava metode filtriranja i mapira rezultat u DTO, što ovu logiku čini teškom za ponovno korištenje i testiranje u izolaciji.

Good example

New codets
1// src/controllers/ArticleController.ts
2export const getTagTeasers = async (req: Request, res: Response) => {
3 const { tagName } = req.params;
4 // GOOD: Controller calls a semantic method that returns exactly what is needed
5 const teasers = await ArticleRepository.getTagLandingPageTeasers(tagName);
6 res.json(teasers);
7};
8
9// src/repositories/ArticleRepository.ts
10class ArticleRepository {
11 public async getTagLandingPageTeasers(tagName: string): Promise<ITeaser[]> {
12 const articles = await this.byTagName(tagName)
13 .filterByPaid(false)
14 .limit(10)
15 .execute();
16
17 return articles.map(article => article.asLandingPageTeaser());
18 }
19}

Explanation (EN)

The specific query logic and data transformation are encapsulated within the Repository. The controller remains thin and focuses solely on handling the request and response.

Objašnjenje (HR)

Specifična logika upita i transformacija podataka enkapsulirani su unutar Repozitorija. Kontroler ostaje 'tanak' i fokusira se isključivo na upravljanje zahtjevom i odgovorom.