A certification exam practice platform inspired by CertyIQ. Features a sign-in page, timed quiz engine, score tracking, and Stripe-powered payments.
| Feature | Description |
|---|---|
| π Sign-In / Guest Access | Email + password login form with guest option. JWT-based auth with role support. |
| β±οΈ Countdown Timer | Each exam session is timed (default 30 minutes). The timer turns red in the last 5 minutes and auto-submits on expiry. |
| π Quiz Engine | Dynamic question rendering with radio-button options. |
| π Score & Results | Instant score calculation and percentage display after submission. |
| π Retake | Users can retake the quiz as many times as they like. |
| π³ Stripe Payments | Secure payment flow using the Payment Intents API with webhook verification. |
| π‘οΈ Security | Helmet headers, CORS, rate limiting, input validation, env-based config. |
Quiz-App/
βββ src/ # β Production backend
β βββ server.js # Entry point β starts Express, graceful shutdown
β βββ app.js # Express app setup (middleware, routes)
β βββ config/
β β βββ index.js # Centralised env-based configuration
β βββ routes/
β β βββ index.js # Route aggregator (API v1)
β β βββ health.js # GET /api/v1/health
β β βββ payment.js # Payment routes (create-payment-intent, status)
β β βββ auth.js # Auth routes (register, login, guest, me)
β βββ controllers/
β β βββ payment.controller.js # Payment request handlers
β β βββ auth.controller.js # Auth request handlers
β βββ services/
β β βββ stripe.service.js # Stripe Payment Intents + webhook logic
β β βββ auth.service.js # JWT signing, guest sessions, password hashing
β βββ middleware/
β β βββ error-handler.js # Centralised error + 404 handler
β β βββ rate-limiter.js # Rate limiters (general, payment, auth)
β β βββ auth.js # JWT verification, guest pass-through, RBAC
β β βββ validate.js # express-validator result checker
β βββ models/
β β βββ schemas.js # PostgreSQL schema definitions (reference)
β βββ utils/
β βββ logger.js # Pino structured logger
βββ public/ # β Static frontend (served by Express)
β βββ Index.html
β βββ script.js
β βββ style.css
βββ exam-practice-pro/ # β Next.js frontend (separate deployment)
βββ .env.example # Environment variable reference
βββ Dockerfile # Production container
βββ docker-compose.yml # Local dev stack
βββ render.yaml # Render deployment config
βββ .github/workflows/ci.yml # CI/CD pipeline
- Node.js v18 or later
- A Stripe account (for payment processing)
git clone https://github.com/yankeeDamn/Quiz-App.git
cd Quiz-Appnpm installcp .env.example .envEdit .env and fill in your values:
NODE_ENV=development
PORT=3000
# Stripe β get keys from https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# JWT β generate with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_SECRET=your-random-secret
CORS_ORIGINS=http://localhost:3000
LOG_LEVEL=info# Development (with auto-reload)
npm run dev
# Production
npm startThe app will be available at http://localhost:3000.
| Variable | Required | Description | Example |
|---|---|---|---|
NODE_ENV |
No | Environment mode | development, production |
PORT |
No | Server port (default: 3000) | 3000 |
STRIPE_SECRET_KEY |
Yes (prod) | Stripe secret key | sk_test_... or sk_live_... |
STRIPE_PUBLISHABLE_KEY |
Yes (prod) | Stripe publishable key | pk_test_... or pk_live_... |
STRIPE_WEBHOOK_SECRET |
Yes (prod) | Stripe webhook signing secret | whsec_... |
JWT_SECRET |
Yes (prod) | Secret for JWT signing | Random 64-byte hex string |
JWT_EXPIRES_IN |
No | JWT expiration (default: 24h) | 24h, 7d |
CORS_ORIGINS |
No | Comma-separated allowed origins | https://example.com |
LOG_LEVEL |
No | Pino log level (default: info) | debug, info, warn |
| Test Mode | Live Mode | |
|---|---|---|
| Keys | sk_test_... / pk_test_... |
sk_live_... / pk_live_... |
| Dashboard | Toggle "Test mode" in Stripe Dashboard | Default view |
| Cards | Use 4242 4242 4242 4242 |
Real cards |
| Webhooks | Use Stripe CLI for local testing | Configure in Dashboard |
-
Local development β use Stripe CLI:
stripe listen --forward-to localhost:3000/api/v1/payments/webhook
Copy the webhook signing secret (
whsec_...) to your.envfile. -
Production β create a webhook endpoint in the Stripe Dashboard:
- URL:
https://your-domain.com/api/v1/payments/webhook - Events:
payment_intent.succeeded,payment_intent.payment_failed - Copy the signing secret to your environment variables.
- URL:
Client Server Stripe
β β β
ββ GET /api/v1/payments/config βββΊ β
ββββ { publishableKey } βββββββ€ β
β β β
ββ POST /create-payment-intent βββΊ β
β βββ stripe.paymentIntents.create βββΊ
β ββββ { clientSecret } ββββββββββ€
ββββ { clientSecret } βββββββββ€ β
β β β
ββ stripe.confirmCardPayment(clientSecret) βββββββββββββββββββΊβ
ββββ { paymentIntent: succeeded } ββββββββββββββββββββββββββββ€
β β β
β ββββ webhook: payment_intent.succeeded
β βββ verify signature β
β βββ update user status β
β βββ 200 OK βββββββββββββββββββββΊβ
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Redirects to /api/v1/health |
GET |
/api/v1/health |
Returns server status, uptime, version |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/v1/auth/register |
None | Register with email + password (min 8 chars) |
POST |
/api/v1/auth/login |
None | Login with email + password, returns JWT |
POST |
/api/v1/auth/guest |
None | Create a guest session with limited access |
GET |
/api/v1/auth/me |
Optional | Get current user profile |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/v1/payments/config |
None | Get Stripe publishable key + amount |
POST |
/api/v1/payments/create-payment-intent |
Optional | Create a PaymentIntent (server-authoritative amount) |
GET |
/api/v1/payments/status/:id |
None | Check PaymentIntent status |
POST |
/api/v1/payments/webhook |
Stripe Sig | Stripe webhook (raw body, signature verified) |
# Build the image
docker build -t exam-practice-pro-api .
# Run with env vars
docker run -d \
--name exam-api \
-p 3000:3000 \
-e NODE_ENV=production \
-e STRIPE_SECRET_KEY=sk_live_... \
-e STRIPE_PUBLISHABLE_KEY=pk_live_... \
-e STRIPE_WEBHOOK_SECRET=whsec_... \
-e JWT_SECRET=$(openssl rand -hex 64) \
-e CORS_ORIGINS=https://your-domain.com \
exam-practice-pro-apicp .env.example .env
# Edit .env with your values
docker compose up --buildThe included render.yaml configures both the API and Next.js frontend.
Set the required environment variables in the Render dashboard.
- Push Docker image to ECR
- Create an ECS service or App Runner service
- Set environment variables in task definition / service config
- Point your domain to the load balancer
- Push Docker image to ACR
- Create a Web App for Containers
- Set environment variables in App Settings
- Configure custom domain + SSL
# Railway
railway link
railway up
# Fly.io
fly launch
fly secrets set STRIPE_SECRET_KEY=sk_live_... JWT_SECRET=...
fly deploy| Environment | NODE_ENV |
Stripe Keys | Database |
|---|---|---|---|
| Development | development |
sk_test_... |
Local / SQLite |
| Staging | staging |
sk_test_... |
Staging DB |
| Production | production |
sk_live_... |
Production DB |
| Feature | Implementation | OWASP Reference |
|---|---|---|
| No hardcoded secrets | All secrets via process.env |
A02:2021 β Cryptographic Failures |
| Input validation | express-validator middleware |
A03:2021 β Injection |
| HTTP security headers | helmet middleware |
A05:2021 β Security Misconfiguration |
| CORS configuration | Allowlist-based cors |
A01:2021 β Broken Access Control |
| Rate limiting | express-rate-limit (general + payment + auth) |
A04:2021 β Insecure Design |
| Server-side amount validation | Stripe service validates against catalog | A04:2021 β Insecure Design |
| Webhook signature verification | stripe.webhooks.constructEvent() |
A08:2021 β Software Integrity Failures |
| JWT authentication | jsonwebtoken with configurable secret + expiry |
A07:2021 β Auth Failures |
| Password hashing | bcryptjs with salt rounds=12 |
A02:2021 β Cryptographic Failures |
| Structured logging | Pino β no secrets logged | A09:2021 β Logging Failures |
| Graceful shutdown | SIGTERM/SIGINT handlers | Operational security |
- Modular backend architecture (routes, controllers, services)
- Stripe Payment Intents API with webhook support
- Security hardening (helmet, CORS, rate limiting, validation)
- JWT-based authentication with guest support
- Database schema design (PostgreSQL)
- Health check, API versioning, structured logging
- Dockerfile, docker-compose, CI/CD pipeline
- Real database integration (PostgreSQL / Prisma)
- OAuth providers (Google, GitHub)
- Multiple exam categories (AWS, Azure, CompTIA, etc.)
- Score history and progress dashboard API
- Question explanations and review mode API
- Admin panel for quiz management
Pull requests are welcome! Please open an issue first to discuss what you would like to change.