Skip to content

mfakyol/lynx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lynx

Lynx is a full-stack URL shortener with an authenticated dashboard: create short links, share them behind a configurable public hostname, and review analytics (time ranges, charts, geography, device metadata). This repository is structured as a portfolio-friendly monorepo (React/Vite SPA + Express API + Docker + Google Cloud Run).

Production example: https://lynx.fatihakyol.com


Summary

The app splits a Vite-powered React marketing and dashboard experience from a Node/Express API backed by MongoDB. Authentication is JWT in Authorization (stored client-side via Zustand), with Passport validating protected routes on the API. Visitors reaching your short-link domain (e.g. l.fatihakyol.dev) hit the same API and get an HTTP 302 to the destination while visits are recorded for analytics.

Production ships as two Docker images on Google Cloud Run. Cloud Build deploys the API first, resolves its URL if needed, then builds the SPA with VITE_* build-args baked in at compile time (nginx serves the static site).


What’s in the box

Area Highlights
Marketing Landing page (Hero, sections), responsive Tailwind layout, clear CTAs
Auth Register / login; JWT-secured dashboard session; axios interceptor refreshes guarded routes
Dashboard List short links with copy/share helpers, QR-ready flows where implemented
Analytics Per-link analytics route: charts (Recharts), maps / breakdowns backed by persisted visits
Redirects GET /lynx/:code or a dedicated host (SHORT_LINK_HOST): GET /:code → resolve + analytics + 302
Ops cloudbuild.yaml: build/push API → deploy API → resolve URL → build/push web (VITE args) → deploy web

Tech stack

Frontend (client/)

  • Vite, React 19, TypeScript
  • react-router-dom for /, /login, /auth/register, /dashboard, /dashboard/links/:id/analytics
  • Tailwind CSS, Lucide React
  • Axios service layer (VITE_API_BASE_URL pointing at /api prefix routes)
  • Zustand + Immer for UI/auth state
  • Zod for client-side shapes where used
  • Recharts, simple-maps / world map tooling for geographic views

Backend (server/)

  • Express 4, TypeScript (ESM), tsx in development
  • MongoDB via Mongoose
  • Passport JWT (/api/auth protected handlers)
  • bcrypt for password hashing
  • Zod for request/schema validation (/api/short-links, analytics query parsing, etc.)
  • geoip-lite, request-ip, ua-parser-js plus custom visit payloads for analytics

Infrastructure

  • Docker: server/Dockerfile, client/Dockerfile (multi-stage Node build → nginx for the SPA)
  • Google Cloud Build + Artifact Registry + Cloud Run (API + Web)
  • Secrets: MONGO_URI and JWT_SECRET via Secret Manager, mapped in cloudbuild.yaml (deploy-api)
  • Substitutions drive SHORT_LINK_HOST, VITE_SHORT_LINK_ORIGIN (public short URL prefix), _VITE_API_URL (optional override vs Cloud Run URL + /api)

Skills this project demonstrates

Useful bullets for a portfolio or CV:

  • Full-stack TypeScript with consistent validation (Zod) across API boundaries
  • REST API design: modular routers (auth, short-links, lynx redirect path, landing routes as needed)
  • Secure-ish auth: password hashing (bcrypt), signed JWT usage, guarded Express routes via Passport JWT strategy
  • Product analytics: visit ingestion and aggregation for dashboards (charts + geo UX)
  • Cloud-native delivery: two services, reproducible Docker images, parameterized Cloud Build substitutions
  • Multi-host API behavior: same Express app distinguishes marketing API host vs short-link vanity host
  • DevEx: typed client/server, SPA dev proxy (Vite) to local API /api

Local development

Requirements: Node.js, npm, and a running MongoDB instance.

1. API environment

Never commit secrets. Copy and edit:

cp server/.env.example server/.env

Set at least MONGO_URI, JWT_SECRET.
Optional SHORT_LINK_HOST (hostname only): when set locally, emulate production by sending Host: that matches (easier paths for day-to-day: use GET http://localhost:3001/lynx/<code>).

2. Run API (from server/)

npm install
npm run dev

Default listens on PORT from .env, else 3001.

3. Client environment & dev server (from client/)

Copy the template:

cp client/.env.example client/.env

Typical locals (import.meta.env from client/.env):

Variable Role
VITE_API_BASE_URL e.g. http://localhost:3001/api
VITE_SHORT_LINK_ORIGIN Optional — prod-style short-link base locally
VITE_LINK_PREFIX Fallback for dev /lynx-style URLs, e.g. http://localhost:3001/lynx

On Cloud Run, VITE_API_BASE_URL and VITE_SHORT_LINK_ORIGIN are passed as docker build args in cloudbuild.yaml — no runtime env injection on the web container.

npm install
npm run dev

Open the URL Vite prints (often http://localhost:5173). Point VITE_API_BASE_URL in client/.env at your running API (http://localhost:3001/api in the defaults). vite.config.ts also proxies /api to port 3001 if you rely on relative calls during experimentation.


Production notes

API (lynx-api) Cloud Run vars / secrets

Name Meaning
NODE_ENV, TRUST_PROXY, SHORT_LINK_HOST Set by pipeline
MONGO_URI, JWT_SECRET Secret Manager → env
PORT Cloud Run (default aligns with Dockerfile 8080)

Web (lynx-web) usually needs no env vars at runtime — the SPA is built with VITE_API_BASE_URL and VITE_SHORT_LINK_ORIGIN in the build-web step (_VITE_API_URL, _VITE_SHORT_LINK_ORIGIN, or resolved API URL + /api).

If API or short-link URLs change, rerun the pipeline so the web image is rebuilt with new build-args.

DNS: UI host → web service; API host → API service; short-link host → same API service.

See cloudbuild.yaml substitutions: _SHORT_LINK_HOST, _VITE_SHORT_LINK_ORIGIN, _VITE_API_URL.


Repository layout

.
├── client/              # React + Vite SPA (Docker: Node build → nginx)
│   └── nginx/           # SPA nginx site config used in Dockerfile
├── server/              # Express API + MongoDB models & services
└── cloudbuild.yaml      # GCP: API → deploy → web image (`VITE_*` build-args) → deploy web

License

Suggested: release under the MIT License and add a LICENSE file at the repo root with your copyright line. Until then, treat the repo as private / all rights reserved unless you explicitly choose a license.

Never commit .env, API keys, or production secrets.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages