Skip to content

feat: implement #421 debounce search, #422 /v1 prefix, #423 contract …#464

Open
Joyyyb wants to merge 1 commit into
Vatix-Protocol:mainfrom
Joyyyb:feat/421-422-423-424-debounce-v1-contract-loadtest
Open

feat: implement #421 debounce search, #422 /v1 prefix, #423 contract …#464
Joyyyb wants to merge 1 commit into
Vatix-Protocol:mainfrom
Joyyyb:feat/421-422-423-424-debounce-v1-contract-loadtest

Conversation

@Joyyyb

@Joyyyb Joyyyb commented Jun 28, 2026

Copy link
Copy Markdown

…test, #424 load test

Summary

Implements four GitHub issues in a single coordinated change across the NestJS API and Next.js web app.


closes #421 — Debounce search API calls from the web app

Problem: Every keystroke in the pool search box fired a new API request immediately, causing unnecessary load and race-condition risk when responses arrive out of order.

Implementation:

  • Created apps/web/hooks/useSearchDebounce.ts — a reusable React hook (useSearchDebounce(value, delay = 300)) that wraps useState + useEffect with a setTimeout / clearTimeout pattern. The debounced value only updates after the user has paused typing for the configured delay (default 300 ms).
  • Replaced the ad-hoc inline debounce in apps/web/app/pools/page.tsx (which used a useRef + manual setTimeout) with a single call to useSearchDebounce(search). This removes ~8 lines of repeated logic and makes the page's intent clearer.
  • The debouncedSearch value is still the one forwarded to usePools(), so no API call is made until the user stops typing.

closes #422 — Version all public REST routes under /v1

Problem: All routes were served at the root (e.g. GET /pools). Without a version prefix there is no non-breaking path to evolve the API contract in the future.

Implementation:

  • Added app.setGlobalPrefix('v1', { exclude: ['health', 'docs', 'docs-json', '/'] }) in apps/api/src/main.ts, immediately after enableShutdownHooks(). NestJS applies the prefix to every registered controller route automatically. The exclusion list keeps GET / (health check), /health, and Swagger docs accessible at their existing paths without a prefix.
  • Updated apps/web/lib/constants.ts: API_BASE now evaluates to ${NEXT_PUBLIC_API_URL ?? 'http://localhost:3001'}/v1. Because every hook in apps/web/hooks/ and every page component derives its fetch URL from API_BASE, this single-line change routes all 15+ fetch calls to the versioned prefix without touching each file individually.
  • Updated apps/api/src/app.smoke.spec.ts to register the same global prefix in its test application bootstrap and changed all route assertions from /pools, /swaps, etc. to /v1/pools, /v1/swaps, etc. so the smoke suite continues to pass.

closes #423 — Contract test for GET /v1/pools list schema

Problem: There was no test that asserted the exact JSON shape returned by the pools list endpoint. A future refactor could silently drop a field (e.g. feeApr) and nothing would catch it.

Implementation:

  • Created apps/api/src/pools/pools.contract.spec.ts — a Jest suite that boots the full NestJS AppModule with the same stub strategy used by app.smoke.spec.ts (Prisma, Redis, and BullMQ replaced with lightweight jest mocks so no live database is required).
  • The spec registers the /v1 global prefix in the test app and then makes four assertions via supertest:
    1. GET /v1/pools returns 200 with the envelope shape { items, page, limit, total, totalPages, orderBy }.
    2. Every object in items contains all required PoolListItem fields: id, token0, token1, feeTier, tvl, volume24h, feeApr, currentPrice.
    3. The ?search= query param is accepted without breaking the schema.
    4. When no pools match, items is an empty array (not null) and total is 0.

closes #424 — Load test script for GET /v1/pools at scale

Problem: There was no automated way to measure throughput or tail latency of the pools list endpoint under concurrent load.

Implementation:

  • Created apps/api/scripts/load-test-pools.js — a self-contained Node.js 18+ script (zero external dependencies; uses native fetch and performance.now()).
  • Accepts three optional positional arguments: concurrency (default 10), total requests (default 100), and base URL (default http://localhost:3001).
  • Implements a bounded-concurrency dispatcher: up to N requests are in-flight at once; as each completes a new one is dispatched until the total is reached.
  • Prints p50 / p95 / p99 latencies, min/max, total elapsed time, throughput (req/s), and a per-status-code error breakdown.
  • Exits with code 1 if any request fails so it can be wired into CI or a performance gate.

Closes #421
Closes #422
Closes #423
Closes #424

Summary

Related issue

  • Closes #

Type of change

  • Bug fix
  • New feature
  • Docs update
  • Chore / refactor

Checklist

  • CI passes (lint + tests + build)
  • Self-reviewed the diff
  • Added or updated tests where relevant
  • Docs updated if needed

…/v1 prefix, Vatix-Protocol#423 contract test, Vatix-Protocol#424 load test

## Summary

Implements four GitHub issues in a single coordinated change across the
NestJS API and Next.js web app.

---

### Vatix-Protocol#421 — Debounce search API calls from the web app

**Problem:** Every keystroke in the pool search box fired a new API
request immediately, causing unnecessary load and race-condition risk
when responses arrive out of order.

**Implementation:**
- Created `apps/web/hooks/useSearchDebounce.ts` — a reusable React
  hook (`useSearchDebounce(value, delay = 300)`) that wraps
  `useState` + `useEffect` with a `setTimeout` / `clearTimeout`
  pattern. The debounced value only updates after the user has paused
  typing for the configured delay (default 300 ms).
- Replaced the ad-hoc inline debounce in `apps/web/app/pools/page.tsx`
  (which used a `useRef` + manual `setTimeout`) with a single call to
  `useSearchDebounce(search)`. This removes ~8 lines of repeated logic
  and makes the page's intent clearer.
- The `debouncedSearch` value is still the one forwarded to
  `usePools()`, so no API call is made until the user stops typing.

---

### Vatix-Protocol#422 — Version all public REST routes under /v1

**Problem:** All routes were served at the root (e.g. `GET /pools`).
Without a version prefix there is no non-breaking path to evolve the
API contract in the future.

**Implementation:**
- Added `app.setGlobalPrefix('v1', { exclude: ['health', 'docs',
  'docs-json', '/'] })` in `apps/api/src/main.ts`, immediately after
  `enableShutdownHooks()`. NestJS applies the prefix to every
  registered controller route automatically. The exclusion list keeps
  `GET /` (health check), `/health`, and Swagger docs accessible at
  their existing paths without a prefix.
- Updated `apps/web/lib/constants.ts`: `API_BASE` now evaluates to
  `${NEXT_PUBLIC_API_URL ?? 'http://localhost:3001'}/v1`. Because every
  hook in `apps/web/hooks/` and every page component derives its fetch
  URL from `API_BASE`, this single-line change routes all 15+ fetch
  calls to the versioned prefix without touching each file individually.
- Updated `apps/api/src/app.smoke.spec.ts` to register the same global
  prefix in its test application bootstrap and changed all route
  assertions from `/pools`, `/swaps`, etc. to `/v1/pools`,
  `/v1/swaps`, etc. so the smoke suite continues to pass.

---

### Vatix-Protocol#423 — Contract test for GET /v1/pools list schema

**Problem:** There was no test that asserted the exact JSON shape
returned by the pools list endpoint. A future refactor could silently
drop a field (e.g. `feeApr`) and nothing would catch it.

**Implementation:**
- Created `apps/api/src/pools/pools.contract.spec.ts` — a Jest suite
  that boots the full NestJS `AppModule` with the same stub strategy
  used by `app.smoke.spec.ts` (Prisma, Redis, and BullMQ replaced with
  lightweight jest mocks so no live database is required).
- The spec registers the `/v1` global prefix in the test app and then
  makes four assertions via supertest:
  1. `GET /v1/pools` returns 200 with the envelope shape
     `{ items, page, limit, total, totalPages, orderBy }`.
  2. Every object in `items` contains all required `PoolListItem`
     fields: `id`, `token0`, `token1`, `feeTier`, `tvl`,
     `volume24h`, `feeApr`, `currentPrice`.
  3. The `?search=` query param is accepted without breaking the schema.
  4. When no pools match, `items` is an empty array (not `null`) and
     `total` is 0.

---

### Vatix-Protocol#424 — Load test script for GET /v1/pools at scale

**Problem:** There was no automated way to measure throughput or tail
latency of the pools list endpoint under concurrent load.

**Implementation:**
- Created `apps/api/scripts/load-test-pools.js` — a self-contained
  Node.js 18+ script (zero external dependencies; uses native `fetch`
  and `performance.now()`).
- Accepts three optional positional arguments: concurrency (default 10),
  total requests (default 100), and base URL (default
  `http://localhost:3001`).
- Implements a bounded-concurrency dispatcher: up to N requests are
  in-flight at once; as each completes a new one is dispatched until the
  total is reached.
- Prints p50 / p95 / p99 latencies, min/max, total elapsed time,
  throughput (req/s), and a per-status-code error breakdown.
- Exits with code 1 if any request fails so it can be wired into CI
  or a performance gate.

---

Closes Vatix-Protocol#421
Closes Vatix-Protocol#422
Closes Vatix-Protocol#423
Closes Vatix-Protocol#424
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Load test GET /pools at scale Contract test pools list schema Version prefix /v1 on public routes Debounce search API from web

1 participant