12 — API Mapping (Search Module)
- 1. Endpoint inventory
- 2. Query parameters (exact)
- 3. Success response (exact shape)
- 4. Empty query response
- 5. Error responses (client-relevant)
- 6. Request example (curl)
- 7. Client mapping (widget → endpoint)
- 8. Non-goals
Exact wire contract for global search. Envelope shapes are authoritative from 00-shared/07_API_Conventions.md (success/error envelopes, pagination meta, error codes, rate limits). Endpoint facts from
search.controller.ts,search.service.ts,search-query.dto.ts.
1. Endpoint inventory
| Method | Path | Permission | Source |
|---|---|---|---|
| GET | /api/v1/search | search | search.controller.ts:11,15-16; permissions.constants.ts:93 |
- Base URL
https://api.<domain>/api/v1(00-shared/07 §1). - Auth:
Authorization: Bearer <accessToken>—JwtAuthGuard(search.controller.ts:3,10); Swagger@ApiBearerAuth()(:9). - This is the only endpoint in the module. Plan-only:
(planned)coaching-index extension needs no new endpoint (sameGET /search—docs/IMPLEMENTATION_PLAN.md:771).
2. Query parameters (exact)
| Param | Type | Default | Constraint | Source |
|---|---|---|---|---|
q | string | — | optional, no length cap | search-query.dto.ts:6-9 |
entityType | string | — | optional, free-form (client sends known types) | search-query.dto.ts:11-14 |
page | int | 1 | ≥ 1 | search-query.dto.ts:16-21 |
limit | int | 20 | 1..50 | search-query.dto.ts:23-29 |
Out-of-range page/limit, non-integer values → 400 VALIDATION_ERROR
(00-shared/07 §3).
3. Success response (exact shape)
{
"success": true,
"message": "OK",
"data": [
{
"entityType": "Student",
"entityId": "66f1c0a5e8b2c1d4f5a6b7c8",
"title": "Rahul Sharma",
"description": "rahul@school.edu",
"tags": ["grade5"]
}
],
"meta": { "page": 1, "limit": 20, "totalItems": 1, "totalPages": 1,
"hasNext": false, "hasPrevious": false },
"timestamp": "2026-08-03T09:00:00.000Z",
"requestId": "req-…"
}
data= array ofSearchResult(search.service.ts:10-16,51-57).meta=buildPaginationMeta(page, limit, totalItems)(pagination-query.dto.ts:41-55); notetotalPagesnever drops below 1 (:46).totalItems= full-text count (search.service.ts:37); whenentityTypefiltering trims a page,totalItemsis replaced by the filtered page length (search.service.ts:44-49) — see QA-14 §7.- Relevance score (
textScore) is used for sort only and never serialized (search-index.repository.ts:28-33;search.service.ts:51-57).
4. Empty query response
q blank/whitespace → data: [], meta.totalItems: 0, totalPages: 1
(search.service.ts:28-30). 200, not an error.
5. Error responses (client-relevant)
| HTTP | Code | When | Source |
|---|---|---|---|
| 400 | VALIDATION_ERROR | invalid page/limit/types | search-query.dto.ts; 00-shared/07 §3 |
| 401 | UNAUTHENTICATED | missing/expired token | JwtAuthGuard (search.controller.ts:10) |
| 403 | PERMISSION_DENIED | no search permission | @Permissions('search') (search.controller.ts:16; permissions.constants.ts:93) |
| 429 | RATE_LIMITED | per-tier limits (00-shared/07 §4) | global rate limit guards |
| 5xx | INTERNAL_SERVER_ERROR | internal (incl. regex-fallback failure) | 00-shared/07 §3 |
Server-side resilience: $text failure is caught and retried via regex
fallback before any error reaches the client (search.service.ts:35-41).
6. Request example (curl)
GET /api/v1/search?q=rahul%20sharma&entityType=Student&page=1&limit=20
Authorization: Bearer <accessToken>
7. Client mapping (widget → endpoint)
Screen (05) | Request | Key handling |
|---|---|---|
| Search bar | GET /search?q=… (debounced 300 ms) | drop stale responses (13 §3) |
| Results screen | same + infinite scroll (page++) | append; meta.hasNext gate (pagination-query.dto.ts:52) |
| See-all | &entityType=X | replace list, page 1 |
| Refresh | same query, page 1 | replace list |
8. Non-goals
- No
POST /search, no bulk, no suggestions, no history endpoints — anything beyondGET /searchis(forward-looking)/(planned). - Search results are not cached in Redis today —
RedisCacheService(redis-cache.service.ts:16-19) is available but unwired(proposed).