Track real-time stock prices, get personalized alerts, and explore detailed company insights.
A full-stack stock market dashboard built with Next.js 16, MongoDB, Better Auth, Inngest, and TradingView.
π Visit Live Β β’Β π Report a Bug Β β’Β β¨ Request a Feature
- Overview
- Try It
- Features
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- Available Scripts
- Deployment
- CI/CD
- Contributing
- License
StockLens is a market-tracking web application that gives retail investors a single place to watch the market, research individual companies, and receive tailored email updates. It combines live market widgets from TradingView, company/search data from Finnhub, secure email-and-password authentication via Better Auth, and event-driven background workflows powered by Inngest - including an AI-generated personalized welcome email.
Alongside US markets, StockLens covers the Colombo Stock Exchange (CSE) in Sri Lanka. Indices, movers, sector performance and every listed security are read live from the exchange's own public JSON API
The app is server-first (Next.js App Router + React Server Components), styled with Tailwind CSS v4 and shadcn/ui, and deploys to Vercel.
Disclaimer: StockLens is an informational tool. It does not provide investment, financial, or trading advice.
No sign-up required. On the sign-in page, click "Explore the demo" to enter with a temporary guest account and browse the full app.
Under the hood this uses Better Auth's anonymous plugin to create a sandboxed, flagged session
(isAnonymous: true). Demo accounts are rate-limited (5 per IP / hour), blocked from write actions
via a requireRealUser() guard, and purged automatically after 24 hours by the cleanup-demo-users
Inngest cron. If you later sign up for real, the guest session is linked to your new account.
-
π Authentication :- Email/password sign-up and sign-in with Better Auth, backed by MongoDB. Session-aware layouts automatically redirect authenticated users away from auth pages and unauthenticated users to sign-in.
-
π§ͺ One-click Demo Mode :- A "Try the demo" button spins up a throwaway anonymous session (Better Auth anonymous plugin) so visitors can explore without signing up; guest accounts are rate-limited and swept hourly by an Inngest cron.
-
π Market Dashboard :- Live Market Overview, Stock Heatmap, Top Stories, and Market Data widgets embedded from TradingView.
-
π’ Stock Details Page :- Symbol info, advanced candlestick chart, baseline chart, technical-analysis gauge, company profile, and company financials for any ticker.
-
β Watchlist :- Follow any company and it is saved to your account
-
π±π° Colombo Stock Exchange :- A full Sri Lanka section reading live from the CSE's own JSON API at
cse.lk: ASPI and S&P SL20 index cards, market status, turnover and trade counts, top gainers / losers / most active, sector indices, and every listed security in one sortable, searchable table. Each ticker gets a/cse/[symbol]page with company info, day and 52-week price ranges, and recent trades. CSE symbols are merged into βK search next to US results and can be saved to the same watchlist, priced in LKR. Responses are cached server-side (60s for live data, 1h for reference data), and one market-widetradeSummaryfeed backs the table, search and watchlist hydration - so there is no per-symbol fan-out against the exchange. -
π Command-Palette Search :-
βK/Ctrl+Ksearch dialog that queries Finnhub (US) and the CSE feed (Sri Lanka) in parallel and interleaves the two, with a debounced input and a fallback list of popular stocks. -
βοΈ Transactional Email :- HTML email templates (welcome, news summary, price/volume alerts, inactive-user reminder) delivered through Nodemailer.
-
π€ AI-Personalized Onboarding :- On sign-up, an Inngest function calls an Anthropic model (
claude-haiku-4-5) to generate a welcome-email intro tailored to the user's stated investment goals, risk tolerance, and preferred industry.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, RSC, Turbopack), React 19 |
| Language | TypeScript 5 |
| Styling | Tailwind CSS v4, shadcn/ui, Base UI, tw-animate-css |
| UI / UX | lucide-react, Sonner (toasts), next-themes, cmdk |
| Auth | Better Auth (MongoDB adapter, Next.js cookies plugin, anonymous plugin for demo mode) |
| Database | MongoDB via Mongoose 9 (cached connection) |
| Background Jobs | Inngest (event-driven functions + AI inference step) |
| Forms | react-hook-form, react-select-country-list |
| Nodemailer (Gmail transport) | |
| Market Data | Finnhub API (US symbol search & company profiles), TradingView Widgets (charts), Colombo Stock Exchange API (Sri Lanka indices, securities & trades) |
| Tooling | ESLint 9 (eslint-config-next), Turbopack |
| Hosting | Vercel |
flowchart TD
User([User / Browser])
subgraph Next["Next.js 16 App (Vercel)"]
Auth["(auth) routes\nsign-in / sign-up"]
Root["(root) routes\ndashboard + /stocks/[symbol]"]
Cse["(root)/cse routes\nSri Lanka overview + /cse/[symbol]"]
Search["SearchCommand (βK)"]
Actions["Server Actions\nauth / finnhub / watchlist"]
CseActions["cse.actions\nunstable_cache 60s / 1h"]
InngestRoute["/api/inngest route"]
end
subgraph External["External Services"]
Mongo[("MongoDB Atlas")]
BetterAuth["Better Auth"]
Finnhub["Finnhub API"]
CseApi["Colombo Stock Exchange\ncse.lk JSON API"]
TradingView["TradingView Widgets"]
Inngest["Inngest Cloud"]
Anthropic["Anthropic (Claude)"]
Gmail["Gmail / SMTP"]
end
User --> Auth
User --> Root
User --> Cse
User --> Search
Root --> TradingView
Cse --> CseActions --> CseApi
Search --> Actions --> Finnhub
Search -- "merged results" --> CseActions
Auth --> Actions
Actions --> BetterAuth --> Mongo
Actions -- "CSE watchlist rows" --> CseActions
Actions -- "app/user.created" --> Inngest
Inngest --> InngestRoute
InngestRoute --> Anthropic
InngestRoute --> Gmail
Key flows
- Sign-up -
signUpWithEmailserver action creates the user through Better Auth (stored in MongoDB), then emits anapp/user.createdInngest event with the user's profile. - Welcome email - The
sendSignUpEmailInngest function receives the event, calls Claude to generate a personalized intro paragraph, then sends the welcome email via Nodemailer. - Search - The
SearchCommanddialog debounces input and calls thesearchStocksserver action, which fans out to Finnhub (symbol search, or popular-symbol profiles when the query is empty) and the CSE feed in parallel, normalizes both to a common shape, and interleaves them so Sri Lankan matches are never buried under US ones. Either side failing degrades to the other. - Charts - Dashboard and stock-detail pages render TradingView embed widgets client-side via
the
TradingViewWidgetcomponent anduseTradingViewWidgethook. - Colombo Stock Exchange -
/cserenders on the server:cse.actionsPOSTs form-encoded requests to thecse.lkJSON API, wrapped inunstable_cache(60s for live data, 1h for reference data) and taggedcse. Every call falls back to an empty result on failure, so one dead endpoint degrades a single card instead of blanking the page./cse/[symbol]reads the same cached feeds for company info, price ranges and detailed trades - no API key required. - Cross-market watchlist - Watchlist rows carry a
marketflag (US|CSE). Hydration prices US rows from Finnhub and resolves every CSE row from one market-widetradeSummarycall, so a mixed watchlist costs a single extra request no matter how many Sri Lankan holdings it has. - Demo mode -
startDemoSessioncallssignInAnonymous; the hourlycleanup-demo-usersInngest cron deletes anonymous users (and their sessions/accounts) older than 24 hours.
my-app/
βββ app/
β βββ (auth)/ # Unauthenticated area (redirects to / if logged in)
β β βββ sign-in/page.tsx
β β βββ sign-up/page.tsx
β β βββ layout.tsx
β βββ (root)/ # Authenticated area (redirects to /sign-in if logged out)
β β βββ page.tsx # Market dashboard
β β βββ stocks/[symbol]/ # Stock details
β β βββ watchlist/ # Saved companies
β β βββ cse/ # Colombo Stock Exchange overview
β β βββ cse/[symbol]/ # CSE security details
β β βββ layout.tsx
β βββ api/inngest/route.ts # Inngest serve endpoint (GET/POST/PUT)
β βββ layout.tsx # Root layout (fonts, Toaster, theme provider)
β βββ icon.svg # Favicon (compass rose, light + dark)
β βββ apple-icon.png # Apple touch icon
β βββ globals.css # Design tokens + component layer
βββ components/
β βββ ui/ # shadcn/ui + Base UI primitives
β βββ forms/ # Reusable form fields (InputField, SelectField, β¦)
β βββ chart/ # Background field, threshold rail, shared marks
β βββ cse/ # Colombo Stock Exchange tables and cards
β βββ Header.tsx NavItems.tsx UserDropdown.tsx NightLight.tsx
β βββ SearchCommand.tsx TradingViewWidget.tsx WatchlistButton.tsx StockLogo.tsx
β βββ WatchlistTable.tsx NoticeToInvestors.tsx SessionCode.tsx
βββ database/
β βββ mongoose.ts # Cached Mongoose connection helper
βββ hooks/
β βββ useDebounce.ts
β βββ useTradingViewWidget.tsx
βββ lib/
β βββ actions/ # "use server" actions (auth, finnhub, watchlist, cse)
β βββ auth/auth.ts # Better Auth instance (lazy, cached)
β βββ inngest/ # client, functions, prompts
β βββ nodemailer/ # transporter, templates
β βββ constants.tsx # Nav items, form options, TradingView widget configs
β βββ utils.ts # cn() class-name helper
βββ types/
β βββ global.d.ts # Global type declarations
βββ public/ # Static assets (logo, images)
βββ DESIGN.md # Design system: tokens, type ramp, component rules
βββ PRODUCT.md # Product context: users, purpose, constraints
βββ eslint.config.mjs
βββ next.config.ts
βββ tsconfig.json
- Node.js 20+ and npm
- A MongoDB database (a free MongoDB Atlas cluster works)
- A Finnhub API key - free tier at finnhub.io
- No key needed for the Sri Lanka section - the
cse.lkAPI is public and unauthenticated - A Gmail account with an App Password (for sending email)
- (Optional for local background jobs) the Inngest Dev Server
git clone https://github.com/Thinal-Fernando/StockLens.git
cd StockLens # repository root is the Next.js app
npm installCreate a .env.local file in the project root:
# App
NODE_ENV=development
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# MongoDB
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>/<database>?retryWrites=true&w=majority
# Better Auth
BETTER_AUTH_SECRET=<generate-a-long-random-secret>
BETTER_AUTH_URL=http://localhost:3000
# Market data
FINNHUB_API_KEY=<your-finnhub-api-key>
# Inngest / AI workflow
INNGEST_DEV=1
ANTHROPIC_API_KEY=<your-anthropic-api-key>
# Email (Gmail SMTP)
NODEMAILER_EMAIL=<your-gmail-address>
NODEMAILER_PASSWORD=<your-gmail-app-password>Note:
.env*files are gitignored. Never commit real credentials. In production, set these in your Vercel project's Environment Variables settings.
npm run devOpen http://localhost:3000.
Inngest functions (e.g. the personalized welcome email) run through the /api/inngest route. To
exercise them locally, start the Inngest Dev Server alongside npm run dev:
npx inngest-cli@latest devIt auto-discovers the app at http://localhost:3000/api/inngest. Open the Inngest dev dashboard
(usually http://localhost:8288) to inspect events and function runs.
Registered functions:
| Function | Trigger | Purpose |
|---|---|---|
sendSignUpEmail |
app/user.created event |
Generate and send the AI-personalized welcome email. |
cleanup-demo-users |
Cron 0 * * * * |
Delete anonymous demo accounts older than 24 hours. |
| Script | Description |
|---|---|
npm run dev |
Start the development server (Turbopack) on port 3000. |
npm run build |
Create an optimized production build. |
npm run start |
Serve the production build. |
npm run lint |
Run ESLint across the project. |
The app is designed for Vercel:
- Push the repository to GitHub.
- Import the project into Vercel - the framework preset (Next.js) is detected automatically.
- Add every variable from Environment Variables under
Project β Settings β Environment Variables (set
BETTER_AUTH_URLto your production domain). - Deploy. Vercel builds every push to
mainas a production deployment and every pull request as a preview deployment.
Because the frontend and the Inngest
/api/inngestroute are deployed together, connect the project to Inngest Cloud so background functions run in production.
A GitHub Actions workflow runs on every push and pull request to main:
- Lint -
npm run lint - Type-check -
tsc --noEmit - Build -
npm run build
Deployments are handled by Vercel's Git integration. Branch protection on main requires the CI
checks (and the Vercel preview build) to pass before a pull request can be merged, so production is
only ever built from vetted commits.
Contributions are welcome.
- Fork the repository and create a feature branch:
git checkout -b feature/your-feature. - Make your changes and ensure
npm run lintandnpm run buildpass. - Commit using clear, conventional messages (e.g.
feat(search): β¦,fix(auth): β¦). - Open a pull request against
mainwith a description of the change.
Released under the MIT License. Copyright (c) 2026 Thinal Fernando.