Full-stack authentication playground composed of a Vite + React frontend and an Express + TypeScript backend.
The project demonstrates multiple auth strategies (traditional email/password, Google OAuth2, AWS Cognito hooks) and ships with an offline-friendly mock layer so you can exercise the complete flow without a running database.
- Tech Stack
- App Features
- Project Layout
- Getting Started
- Environment Configuration
- Working With Mock Data
- Available Scripts
- API Notes
- Troubleshooting
Frontend
- React 19 + TypeScript
- Vite 7 (dev server & build)
- React Router, React Query, React Hook Form
- Tailwind CSS 3 + PostCSS + autoprefixer
- react-hot-toast for notifications
Backend
- Node.js 22 / Express 5
- TypeScript + ts-node for dev,
tscbuild output - JWT auth with
jsonwebtoken - Sequelize ORM (MySQL dialect) with optional AWS Cognito & Google OAuth2 integrations
- Nodemailer for transactional email placeholders
- Swagger (swagger-autogen + swagger-ui-express) for API docs
Tooling
- pnpm workspace (
frontend+backend) - ESLint, Prettier, Tailwind formatter
- Email/password registration, login, verification, password reset
- Optional Google OAuth2 login flow (requires credentials file)
- AWS Cognito route scaffolding for alternative auth providers
- Protected React routes using JWT stored in
localStorage - React Query powered data fetching with graceful “Network Error” handling and mock fallbacks
- Swagger UI exposed at
/swagger - Mock repository with seed users for local development when no DB host is configured
.
├── backend/ # Express API (TypeScript)
│ ├── src/
│ │ ├── controllers/ # Auth logic
│ │ ├── routes/ # /api endpoints (/user, /oauth/google, /aws/cognito)
│ │ ├── services/ # Express app + database bootstrap
│ │ ├── mocks/ # Offline repository + seed users
│ │ └── utils/ # JWT helpers, config loader, etc.
│ ├── env-config.json # Default environment config (see below)
│ └── swagger-output.json # Generated by swagger-autogen
└── frontend/ # React app (Vite)
├── src/
│ ├── features/ # Authentication hooks/screens
│ ├── pages/ # Login/Signup/Users/Settings
│ └── ui/ # Layout, ProtectedRoute, shared components
└── vite.config.ts # Dev server proxy + env loading
-
Install prerequisites
- Node.js ≥ 20.19 (project was tested on Node 22.21.1).
Recommendnvm install 22 && nvm use 22. - pnpm ≥ 8 (
npm install -g pnpm).
- Node.js ≥ 20.19 (project was tested on Node 22.21.1).
-
Install dependencies (from repo root):
pnpm install
-
Run the backend:
pnpm --filter backend dev # watches src/ via nodemon + ts-node # or build & run compiled output pnpm --filter backend build pnpm --filter backend start
-
Run the frontend:
pnpm --filter frontend dev # Vite dev server on http://localhost:5173The dev server proxies
/api/*tohttp://localhost:3000by default, so the browser can reach the Express API without CORS issues. -
Visit the app at http://localhost:5173 (login/signup, then explore
/usersand/settingsroutes).
| Key | Description |
|---|---|
SERVER_PORT |
Express port (default 3000) |
JWT_SECRET |
Secret used to sign JWTs |
DB_HOST, DB_PORT, DB_DATABASE, DB_USER, DB_PASSWORD |
MySQL connection info (leave DB_HOST blank or as "db host" to use the mock repository) |
MAILER_AUTH_USER, MAILER_AUTH_PASSWORD |
Used by nodemailer helpers |
Optional JSON files (placed in backend/):
google.oauth2.keys.json— setweb.client_id,client_secret, andredirect_uristo enable the Google OAuth flow inbackend/src/routes/googleOauth2.ts.aws.cognito.json— values consumed by Cognito routes/controllers.
The backend automatically falls back to the mock repository when DB credentials are missing. Once a real database is configured, Sequelize is used transparently.
| Key | Purpose |
|---|---|
VITE_API_URL |
Absolute API base URL used in production builds (falls back to window.location.origin + /api). |
VITE_DEV_SERVER_PROXY_TARGET |
Override target for Vite’s /api proxy during pnpm dev (default http://localhost:3000). |
Example:
VITE_API_URL=https://your-domain.com/api
VITE_DEV_SERVER_PROXY_TARGET=http://localhost:3000- Seed users are stored in
backend/src/mocks/data/users.json. Default entries:demo@example.com/Password123!(verified)guest@example.com/Password123!(not verified)
- When the DB is not configured, these users are served through the mock repository located at
backend/src/mocks/mockUserRepository.ts. - Controllers interact with
userRepository, so both mock and real database paths use the same code paths (JWT issuance, password hashing, verification, password reset, etc.). - Update
users.jsonto add more demo accounts. Passwords must be bcrypt hashes; you can generate one via:cd backend && node -e "const bcrypt=require('bcrypt');bcrypt.hash('YourPassword',10).then(console.log)"
From repository root:
| Command | Description |
|---|---|
pnpm install |
Install all workspace dependencies |
pnpm --filter backend dev |
Start Express in watch mode (nodemon + ts-node) |
pnpm --filter backend build |
Compile TypeScript backend to backend/dist |
pnpm --filter backend start |
Run compiled backend (node dist/index.js) |
pnpm --filter backend swagger-autogen |
Regenerate swagger-output.json |
pnpm --filter frontend dev |
Start Vite dev server |
pnpm --filter frontend build |
Type-check + bundle the React app |
pnpm --filter frontend preview |
Preview the production build |
Each package also exposes lint and format scripts (ESLint + Prettier).
- Base path:
/api - Auth routes (
/api/user/...):POST /signup– create user (sends verification email placeholder)POST /login– returns JWTGET /user– returns current user (requiresAuthorization: Bearer <token>)GET /users– list all users (protected)PUT /verfiy-email,PUT /forgot-password,PUT /reset-password– flows for verification/reset
- AWS Cognito routes live under
/api/aws/cognito/**with similar semantics. - Google OAuth routes:
GET /api/oauth/google/auth-url– retrieve Google consent URLGET /api/oauth/google/callback– handles Google redirect, issues JWT, then redirects back to the frontend.
- Swagger UI:
http://localhost:3000/swagger
The frontend expects /api/user/user to respond with:
{
"code": 200,
"message": "ok",
"data": {
"id": 1,
"name": "Demo User",
"picture": null,
"email": "demo@example.com",
"is_verified": true
}
}When the API returns an error, the React Query hook displays a friendly fallback message and (when applicable) the mock user defined in frontend/src/services/apiAuth.tsx.
- “Network Error” in the frontend — ensure the backend is running on
localhost:3000, or setVITE_DEV_SERVER_PROXY_TARGET/VITE_API_URLto the actual host. - Backend refuses to start — confirm you’re on Node ≥ 20.19 and have run
pnpm install. - JWT/verification errors — set a valid
JWT_SECRETand keep clock skew in mind; tokens default to the settings defined inbackend/src/utils/authenticate.ts. - Need to test offline — temporarily set
DB_HOSTto"db host"(default) to force the mock repository. - Swagger output stale — rerun
pnpm --filter backend swagger-autogenwhenever you change controllers/routes.
Happy hacking! Feel free to adapt the mock layer or extend the auth flows as you integrate with real infrastructure.