DailyNews adopts Domain-Driven Design (DDD) to keep the codebase maintainable, testable, and easy to extend. DDD separates concerns into three concentric layers — domain, application, and infrastructure — so that business rules never depend on database drivers or HTTP libraries, and external dependencies (scrapers, databases) can be swapped without touching the core logic. This also means each layer can be unit-tested in isolation: domain models have zero external imports, services can be tested with mock repositories, and infrastructure implementations are covered by integration tests.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/miikorz/DailyNews/llms.txt
Use this file to discover all available pages before exploring further.
Three-Layer Architecture
Domain
Defines what a news item is. Contains the
Feed interface and FeedDTO class. No framework imports, no database drivers — pure TypeScript.Application
Defines what the system does.
FeedService orchestrates CRUD and scraping. ScrapperService delegates to a pluggable scraper implementation.Infrastructure
Defines how things are stored and fetched.
FeedRepository talks to MongoDB via Mongoose. ElPaisScrapperRepository and ElMundoScrapperRepository parse HTML with Cheerio.Domain Layer
Path:backend/src/domain/model/
The domain layer is the innermost ring. It contains the Feed interface — the canonical shape of a news item across the entire application — and FeedDTO, a plain class that constructs and serialises that shape. Neither file imports anything outside of TypeScript’s standard library.
Application Layer
Path:backend/src/application/services/
The application layer contains the use-case logic. It depends only on the domain model and on interfaces defined in the infrastructure layer — never on concrete classes.
FeedService is the main orchestrator. It receives a FeedRepositoryInterface and an array of ScrapperRepositoryInterface implementations via its constructor. When getAllFeeds() is called, it iterates every registered scraper, collects results, persists them, and returns the full combined feed:
ScrapperService is a thin wrapper that holds a single ScrapperRepositoryInterface and exposes getTopNews(). Its sole purpose is to decouple FeedService from needing to know how to call a scraper directly:
Infrastructure Layer
Path:backend/src/infrastructure/
The infrastructure layer contains every class that talks to the outside world: MongoDB via Mongoose, and newspaper websites via Cheerio.
FeedRepository implements FeedRepositoryInterface using the FeedODMModel Mongoose model. saveScrappedFeeds() deduplicates by URL before inserting, preventing duplicate articles on repeated scrapes. findAll() returns all items sorted newest-first.
ElPaisScrapperRepository fetches https://elpais.com/, loads the HTML with Cheerio, and maps the first five <article> elements to FeedDTO objects:
ElMundoScrapperRepository fetches https://elmundo.es/ and applies the same pattern with El Mundo’s specific CSS selectors. It additionally skips articles that have no link, since those represent video-only cards without a full article page.
Request Flow
Here is exactly what happens when a client callsGET /feed:
Express router dispatches to the controller
backend/src/api/routes.ts maps GET /feed to the getAllFeeds controller function in feedController.ts. Express parses the request and passes req/res to the handler.Controller calls FeedService.getAllFeeds()
The controller instantiates (or resolves) a
FeedService wired with a FeedRepository and both scraper implementations, then calls feedService.getAllFeeds() and awaits the result.FeedService iterates registered scrapers
Inside
getAllFeeds(), FeedService loops over this.scrappers — [ElPaisScrapperRepository, ElMundoScrapperRepository]. For each one it creates a ScrapperService instance and calls scrapperService.getTopNews(), collecting up to 5 articles per source (10 total).Scrapped feeds are deduplicated and persisted
feedRepository.saveScrappedFeeds(scrappedFeeds) checks MongoDB for existing documents with matching link values. Only genuinely new articles are inserted with FeedModel.insertMany(), ensuring idempotent scraping.Dependency Injection
Scrapers and the feed repository are injected intoFeedService through its constructor, not instantiated inside it:
- Swappable scrapers — removing El Mundo or adding a new source is a one-line change at the composition root.
- Testable services — unit tests can pass in-memory mock implementations of both interfaces without touching the database or the network.
- Open/Closed Principle —
FeedServiceis open for extension (add scrapers) and closed for modification.
Extension Points: Interfaces
Two interfaces define the contracts that all implementations must satisfy.FeedRepositoryInterface (backend/src/infrastructure/repositories/feed/FeedRepositoryInterface.ts) declares the full CRUD surface: findAll, findById, findByTitle, create, update, delete, and saveScrappedFeeds.
ScrapperRepositoryInterface (backend/src/infrastructure/repositories/scrapper/ScrapperRepositoryInterface.ts) is intentionally minimal — a single method:
getTopNews(): Promise<Feed[]> can be plugged straight into FeedService as a new news source.