A portfolio project demonstrating production-grade API integration with ROLLER, a booking and ticketing platform used by tourism attractions. Built around a fictional Blue Mountains venue.
Built as a portfolio project to demonstrate real-world API integration patterns. Runs fully on mock data out of the box; switching to the live ROLLER API requires only a single environment variable change.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript |
| Data fetching | TanStack Query |
| Charts | Recharts |
| Timezone | date-fns-tz |
| Validation | Zod |
| Mock data | @faker-js/faker |
| Testing | Vitest |
| Deployment | Vercel |
Browser
└── React components ('use client')
└── useRoller.ts (TanStack Query hooks)
└── rollerFetch('/v1/...')
│
├── USE_MOCK=true → Next.js API Route (/api/v1/...)
│ └── generators.ts (faker data)
│
└── USE_MOCK=false → auth.ts (OAuth2 token)
└── https://api.roller.app/v1/...
Security note: ROLLER_CLIENT_SECRET lives only in server-side environment variables and is never exposed to the browser. Next.js API Routes act as a proxy between the client and the ROLLER API.
git clone https://github.com/your-username/roller-dashboard
cd roller-dashboard
npm install
npm run dev
# → http://localhost:3000The app starts in mock mode (NEXT_PUBLIC_USE_MOCK_API=true) and requires no API keys.
# .env.local
# Toggle mock / production mode
NEXT_PUBLIC_USE_MOCK_API=true
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# Production only — from ROLLER Dashboard → Settings → Integrations → API Keys
# ROLLER_CLIENT_ID=your_client_id
# ROLLER_CLIENT_SECRET=your_client_secret
# CORS allowed origin (set to your Vercel deployment URL)
# NEXT_PUBLIC_APP_URL=https://your-app.vercel.app
# Rate limiting via Upstash Redis (optional — falls back to in-memory)
# UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
# UPSTASH_REDIS_REST_TOKEN=your_token- Set
NEXT_PUBLIC_USE_MOCK_API=false - Add
ROLLER_CLIENT_IDandROLLER_CLIENT_SECRET - Restart the dev server (or redeploy)
No code changes required.
- Fetches a Bearer token from
https://auth.roller.app/oauth/token - Caches the token in module-level memory; reuses it until 30 seconds before expiry
clearTokenCache()is called on 401 responses to force re-authentication
- 401 — clears token cache and retries (up to 2 times)
- 429 — reads
Retry-Afterheader, waits, then retries - 5xx — throws immediately (no retry)
Types are derived from Zod schemas via z.infer<>, eliminating dual maintenance of interfaces and runtime validators. Both query parameters and API responses are validated at the route handler level.
ROLLER returns all timestamps in UTC. date-fns-tz converts them to Australia/Sydney, which automatically handles the AEST (UTC+10) / AEDT (UTC+11) daylight saving transition.
Production builds include X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Strict-Transport-Security, and Content-Security-Policy.
CORS is restricted to the configured app origin. Rate limiting is applied to all /api/* routes: 100 requests per minute per IP, backed by Upstash Redis in production with an in-memory fallback for local development.
npm test # run all tests once
npm run test:watch # watch mode
npm run test:coverage # with coverage report129 tests across 6 files covering (1 skipped):
- OAuth2 token cache boundary values (30-second pre-expiry window)
- 401 / 429 retry logic and edge cases
- Zod schema equivalence partitions and boundary values
- Rate limit window reset (including
now === resetAtboundary) - Middleware CORS and IP resolution logic
- Mock data generators
vercel deploySet the following in Vercel → Settings → Environment Variables:
| Variable | Value |
|---|---|
NEXT_PUBLIC_USE_MOCK_API |
false |
ROLLER_CLIENT_ID |
from ROLLER dashboard |
ROLLER_CLIENT_SECRET |
from ROLLER dashboard |
NEXT_PUBLIC_APP_URL |
your Vercel URL |
UPSTASH_REDIS_REST_URL |
from Upstash (optional) |
UPSTASH_REDIS_REST_TOKEN |
from Upstash (optional) |
- Authentication — the dashboard has no user authentication. For production use, add NextAuth.js with Google Workspace or Microsoft 365 SSO and protect all routes via
middleware.ts. - Rate limit persistence — without Upstash, the in-memory rate limiter is not shared across serverless instances.
- ROLLER API access — the ROLLER API requires a paid subscription add-on. This project uses faker-generated mock data that matches the documented response schema.