A full-stack TypeScript application for fetching stock quotes, validating user input, and emailing a summary report.
Built with:
- Vite + React + TailwindCSS + Zod + React Hook Form
- Node.js + Express + TypeScript + Nodemailer
- Vitest + React Testing Library
- Docker + Docker Compose
This project consists of two main parts:
| Layer | Path | Description |
|---|---|---|
| Frontend | apps/web |
A Vite React app where users request quotes, select a symbol, choose date range, and receive data & charts. |
| Backend | apps/api |
An Express server that validates input, fetches stock quote data (local mocks), and emails the results using Nodemailer. |
The backend exposes a simple REST API consumed by the frontend.
- Form validation powered by Zod + React Hook Form
- Symbol selector with local mock data filtering
(TODO: integrate real live search API) - Responsive data table with sorting
- Recharts line chart for visualizing price trends
- Clean, responsive UI with TailwindCSS
- Comprehensive unit and integration tests with Vitest
- Schema validation shared between frontend and backend via
@xm-schema/shared - Fetches quote data from local mock JSON files
- Sends summary emails via Nodemailer
- Typed responses and unified error handling
GET /api/v1/health
Health check
GET /api/v1/symbols
Returns the list of available symbols for the selector.
Responses are HTTP cached with
Cache-Control: public, max-age={symbolsCacheSeconds}
where symbolsCacheSeconds is configured in the API (env/config). This keeps the dropdown fast and reduces load.
POST /api/v1/quotes
Request OHLC data for a symbol and date range (validated with shared Zod schema); also triggers an email.
- symbolsCacheSeconds – controls Cache-Control max-age for /symbols.
- quotesDir – directory for mock quote JSON files.
- SMTP envs (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, EMAIL_FROM) for email.
├── apps/
│ ├── api/ # Express backend
│ └── web/ # React + Vite frontend
├── packages/
│ └── shared/ # Shared Zod schemas and types
├── docker-compose.yml
└── README.md
All quote data is currently served from local JSON files in apps/api/data/quotes/.
This design allows the project to run fully offline while still returning realistic responses.
apps/
└── api/
└── data/
└── quotes/
├── AAPL.json
├── AAON.json
├── AAME.json
├── AAL.json
├── AAOI.json
├── AAIT.json
└── symbols.json
# Build and start both web & api
docker compose up --buildFrontend: http://localhost:5173 Backend: http://localhost:4000
apps/api/.env.dev
NODE_ENV=prod
PUBLIC_API_URL=http://localhost:4000
PORT=4000
QUOTES_DIR=/app/data/quotes
WEB_ORIGIN=http://localhost:5173
SYMBOLS_FILE_PATH=/app/data/symbols.json
SYMBOLS_CACHE_SECONDS=300
SMTP_HOST=smtp.ethereal.email
SMTP_PORT=587
SMTP_USER=
SMTP_PASS=
SMTP_SECURE=false
EMAIL_FROM=noreply@example.com
apps/web/.env.dev
VITE_API_URL=http://localhost:4000
pnpm install
cd apps/api
pnpm dev
cd apps/web
pnpm dev
Run all tests (frontend + shared) using pnpm:
pnpm --filter ./apps/web testMIT © 2025 Dmitry