Back to Projects

TradeView Lite

A solo, public project exploring the current state of the GraphQL/Next.js ecosystem through a real stock watchlist dashboard

✓

Public, runnable repository, not a resume claim: a real GraphQL BFF + Next.js 16 app anyone can clone and run

✓

Caught real version-drift issues (Next.js 16 caching model, Apollo Client v4 split packages, TanStack Table v9's rewritten API) by reading installed package docs directly

✓

CI runs lint, unit tests, build, e2e, and an axe-core accessibility audit on every push and PR

✓

Verified FMP free-tier constraints (no batch endpoint, no OHLC data) against the live API before designing around them, instead of assuming

Next.js 16
TypeScript
Tailwind
shadcn/ui
GraphQL
Apollo Server
Apollo Client
Zustand
TanStack Table
visx
Zod
Playwright

Overview

A stock watchlist dashboard built solo, outside of work, to get hands-on with what's actually changed in the newer major versions of the GraphQL/Next.js ecosystem, Next.js 16's App Router and caching model, Apollo Client v4's split packages, and TanStack Table v9's rewritten core API, rather than another generic CRUD tutorial. It stays close to a real problem space (a financial dashboard backed by a live market-data API) instead of toy data.

Unlike the case studies above, this one is a real, public repository: github.com/meenakshi-ojha/tradeview-lite. Full architecture reasoning, including the cross-review rounds, lives in docs/architecture.md. It is not deployed, it lives as source and CI only, so there's no live demo link here on purpose.

System Map

Grouped by architectural layer, left to right within each layer are the main pieces TradeView Lite actually uses for that layer, pulled directly from docs/architecture.md, not a generic diagram.

UIStateServicesRoutingInfraBackendWatchlistTanStack Table v9 + VirtualPrice Chartvisx + D3 scalesApp Shell3 routes, shadcn/uiMarkets / SettingsSparklines, detail modalLocal stateuseState / useReducerGlobal + persistZustand + localStorageServer stateApollo useQuery + pollForm validationDirect Zod parseGraphQL BFFApollo Server, Route HandlerMock sourcePseudo-random walkReal sourceFMP REST wrapperError mapping402 / 429 / timeoutApp RouterDashboard, Markets, SettingsData Cacheforce-cache + revalidateMock modeDefault, polls every 30sReal modeNo polling, 1 call/changeCI pipelineLint → test → build → e2eRuntimeBun + Next.js 16Security auditbun audit, every runNot deployedSource + CI onlyFinancial Modeling PrepFree-tier REST APIRate limit250 calls/day, no batchQuote endpoint1 call per symbolTimeoutAbortSignal, 8sTestingbun test + PlaywrightAccessibilityaxe-core, every CI runScope cutsNo auth, DB, i18n, SSR fetchReview processThree-round, cross-checkedSix architectural layers, plus cross-cutting concerns (bottom row, unlabeled)

Architecture

Frontend: Next.js 16 (App Router) + TypeScript + Tailwind + shadcn/ui (Radix primitives). Three real routes, Dashboard, Markets, and Settings, sharing one sidebar shell, not a single page with conditional panels.

Data layer: A self-hosted GraphQL BFF (Apollo Server via @as-integrations/next) mounted as an App Router Route Handler in the same deployment as the frontend, wrapping Financial Modeling Prep's REST API.

State: Zustand with the persist middleware for global state (watchlist, selected symbol, data mode); Apollo Client's own useQuery + pollInterval for server state; direct Zod validation for the single-field add-ticker form.

Watchlist & charts: TanStack Table v9 + TanStack Virtual for a virtualized, sortable grid; visx + D3 scales for line and candlestick price history views.

Key Decisions

GraphQL BFF in the same deployment, not a separate function: this is what actually lets Next's fetch Data Cache apply to the calls the BFF makes to Financial Modeling Prep. A separate serverless function for the BFF would have silently defeated the caching strategy.

One API call per symbol in Real mode, client polling disabled entirely: FMP's free tier has no batch-quote endpoint, verified directly against the live API rather than assumed from docs, so continuous polling would burn the 250-call daily quota fast. Real mode fetches once per toggle or watchlist change instead.

Mock mode as the default everywhere: a deterministic pseudo-random walk with no network calls, so local development and demos can never accidentally burn the real quota.

One mechanism for global state and persistence: Zustand's persist middleware syncs the watchlist, selected symbol, and data mode to localStorage automatically, instead of wiring up a separate persistence layer alongside the store.

Candlestick charts and the indices strip stay mock-only in both modes: FMP's free tier doesn't serve the plain index tickers this app uses, and its historical-price-eod/light endpoint has no OHLC data to build real candles from, again confirmed against the live API, not assumed.

Testing & CI

  • Unit (bun test): ticker validation, the Zustand store's actions, and both data sources, with fetch mocked for the real one.
  • E2E (Playwright): dashboard, watchlist, Markets, and Settings flows, Mock mode only, since Real mode needs a live FMP key that's never committed.
  • Accessibility: an axe-core audit runs as part of the e2e suite against every route, on every CI run.
  • CI (GitHub Actions): lint, then unit tests, then build, then e2e + accessibility audit, then bun audit for dependency vulnerabilities, one sequential job on every push and PR to main.

What I'd Flag in an Interview

  • Framework docs go stale fast: version-drift issues in Next.js 16's caching model, Apollo Client v4's split packages, and TanStack Table v9's API were only caught by reading the installed packages' own docs, not by trusting prior assumptions about how those libraries work.
  • fetch(url, { next: { revalidate } }) alone does nothing in recent Next.js without cache: "force-cache" alongside it, caching is opt-in, and that's easy to get wrong silently.
  • A free tier's real constraints (no batch endpoint, no OHLC data on this particular endpoint) are worth confirming against the live API directly before designing rate-limit math around them.
  • A deliberate three-round architecture review, initial design cross-checked by an independent model, concerns resolved with concrete fixes, then re-reviewed, caught the cache mechanism and rate-limit math issues before any implementation code was written.

Known Scope Cuts

No auth or user accounts, no database (Zustand + localStorage only), no i18n, and no SSR/RSC data-fetching pattern (GraphQL is fetched entirely client-side via Apollo, even though the app runs on Next.js). These are deliberate scope cuts, the point of this project was depth on GraphQL/Next.js/state-management trade-offs and a real test/CI pipeline, not breadth.