Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

04 — Information Architecture (Search Module)

Where global search lives in the app, what it searches, and how results are organized. Backend facts from src/modules/search/**; app-shell conventions from 00-shared/05_Global_Information_Architecture.md.


1. Entry point: the global search bar

Search is a cross-module utility, not a tab. It lives as a persistent search bar in the app shell (top app bar), reachable from every authenticated screen — mirroring the backend's single cross-cutting endpoint (GET /search, search.controller.ts:11-18). Route: /search (results surface); the input itself is an in-shell control that routes to /search on commit.

2. What the index contains (searchable universe)

One denormalized search_indexes collection per tenant (search-index.schema.ts:7); rows are created from domain events (search-indexer.service.ts:7-32):

  • 7 fully-synced types: User, Student, Teacher, Staff, Parent, Book, Organization (create/update/delete).
  • 2 partially-synced types: Lead (create only), Announcement (create/publish only) — no update/delete events (search-indexer.service.ts:29-31).
  • (planned): batches, sessions, test_series, DPP (docs/IMPLEMENTATION_PLAN.md:771).

3. Result organization

The API returns a flat, relevance-ranked list — there is no server-side grouping (search.service.ts:10-16, :51-57). The client groups by entityType with headers (see 06 §4). Filtering by type = the optional entityType query param (search-query.dto.ts:11-14).

/search
├── Group: Students (entityType=Student)
│   └── SearchResultTile × n (title, description, tags)
├── Group: Books
│   └── ...
└── Group: Announcements
    └── ...

Group order is client-proposed (people first, then content) — the server does not order groups; ordering by textScore happens inside each flat list (search-index.repository.ts:28-33).

RoutePurposeBacking
/searchresults surface for current queryGET /search?q=…
/search?type=Studentsee-all drill-downentityType param (search-query.dto.ts:11-14)
/search/result/<entityType>/<entityId> (proposed)deep-link to a specific resultkey = (entityType, entityId) (search.service.ts:11-12)

Result rows deep-link into the owning module's detail route (Students → /students/:id, etc.) — no dedicated result-detail screen exists; the module only returns pointers (entityId, entityType).

5. What is NOT in this IA

  • No search history / recent searches / saved searches / suggestions(forward-looking), nothing server-side.
  • No per-entity search endpoints — other modules' lists use their own q filters (pagination-query.dto.ts:26-29); this module is the global aggregator only.
  • No cross-tenant traversal — every row and query carries tenantId (base.schema.ts:9-31; search-index.repository.ts:84-89).
  • No results cache in the data path — Redis is tenant-namespaced and available (redis-cache.service.ts:16-19) but unused by SearchService (proposed).

6. Navigation summary

FromToTrigger
Any shell screen/searchtap search bar / type
/search/students/:id, /books/:id, …tap result row (owner module route)
/search/search?type=X"See all X" chip
/search?type=X/searchclear type filter