Appearance
Mock API Layer
DoCurious uses a mock-first API architecture. Every API module has both a mock implementation and a real implementation. The app switches between them at build time based on the VITE_USE_MOCK_API environment variable.
Architecture
Configuration
The API configuration lives in src/api/config.ts:
typescript
export const apiConfig = {
baseUrl: import.meta.env.VITE_API_URL || 'http://localhost:8000/api/portal',
useMockApi: import.meta.env.VITE_USE_MOCK_API !== 'false', // defaults to true
timeout: 30000,
googleClientId: import.meta.env.VITE_GOOGLE_CLIENT_ID || '',
stripePublicKey: import.meta.env.VITE_STRIPE_PUBLIC_KEY || '',
recaptchaSiteKey: import.meta.env.VITE_RECAPTCHA_SITE_KEY || '',
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY || '',
appleClientId: import.meta.env.VITE_APPLE_CLIENT_ID || '',
appleRedirectUri: import.meta.env.VITE_APPLE_REDIRECT_URI || '',
}Note that useMockApi defaults to true unless explicitly set to the string 'false'. This means the app works out of the box without any backend.
Response Types
All API methods return consistent response types defined in src/types/common.types.ts:
typescript
interface ApiResponse<T> {
success: boolean
data: T
error?: string
message?: string
}
interface PaginatedResponse<T> {
success: boolean
data: T[]
error?: string
pagination: {
page: number
limit: number
total: number
totalPages: number
hasMore: boolean
}
}
interface PaginationParams {
page?: number
limit?: number
}Client Utilities
The src/api/client.ts file provides helper functions used by mock API modules:
typescript
// Create a success response
function success<T>(data: T): ApiResponse<T>
// Create an error response
function error<T>(message: string): ApiResponse<T>
// Create a paginated response (auto-slices the array)
function paginated<T>(items: T[], params?: PaginationParams): PaginatedResponse<T>
// Wrap an operation with simulated network delay (100ms default)
async function withDelay<T>(operation: () => T, config?: { delay?: number }): Promise<T>
// Wrap an operation with delay + try-catch → ApiResponse
async function safeExecute<T>(operation: () => T, config?: { delay?: number }): Promise<ApiResponse<T>>
// Mock auth utilities
function mockHash(password: string): string
function mockVerify(password: string, hash: string): boolean
function generateMockToken(userId: string, expiresInHours?: number): string
function decodeMockToken(token: string): { sub: string; exp: number } | null
function isTokenExpired(token: string): boolean
// Validation
function isValidEmail(email: string): boolean
function isValidPassword(password: string): { valid: boolean; message?: string }
function calculateAge(dateOfBirth: string): numberAPI Module Catalog
Each module is exported from src/api/index.ts with automatic mock/real switching:
| Module | Export Name | Mock File | Real File | Purpose | Feature Guide |
|---|---|---|---|---|---|
| Auth | authApi | auth.api.ts | auth.real.api.ts | Login, register, token management, Google/Apple OAuth | Accounts |
| User | userApi | user.api.ts | user.real.api.ts | Profile CRUD, settings, deletion requests | Accounts |
| Challenge | challengeApi | challenge.api.ts | challenge.real.api.ts | Challenge CRUD, search, categories, instances | Challenges |
| Track Record | trackRecordApi | trackRecord.api.ts | trackRecord.real.api.ts | TR CRUD, entries, media, submission | Track Records |
| Explore | exploreApi | explore.api.ts | explore.real.api.ts | Curated views, saved list, search | Explore |
| Community | communityApi | community.api.ts | community.real.api.ts | Community CRUD, feeds, memberships | Communities |
| Gift | giftApi | gift.api.ts | gift.real.api.ts | Send/receive gifts, redemption | Gifting |
| School | schoolApi | school.api.ts | school.real.api.ts | Roster, classes, assignments, grades | School |
| Notification | notificationApi | notification.api.ts | notification.real.api.ts | Notifications, preferences, mark read | Notifications |
| Gamification | gamificationApi | gamification.api.ts | gamification.real.api.ts | XP, badges, streaks, levels | Gamification |
| Learning Path | learningPathApi | learningPath.api.ts | learningPath.real.api.ts | Path CRUD, enrollment, progress | Gamification |
| Portfolio | portfolioApi | portfolio.api.ts | portfolio.real.api.ts | Portfolio creation, PDF export | Track Records |
| Event | eventApi | event.api.ts | event.real.api.ts | Challenge events, registrations | Explore |
| Community Goal | communityGoalApi | communityGoal.api.ts | communityGoal.real.api.ts | Cooperative goals, contributions | Communities |
| Payment | paymentApi | payment.api.ts | payment.real.api.ts | Stripe checkout, payment methods | Vendor |
| Email Verification | emailVerificationApi | emailVerification.api.ts | emailVerification.real.api.ts | Email verification flow | Accounts |
| Invitation | invitationApi | invitation.api.ts | invitation.real.api.ts | Challenge invitations | Gifting |
| Reflection | reflectionApi | reflection.api.ts | reflection.real.api.ts | SEL reflection prompts/responses | Reflection |
| Onboarding | onboardingApi | onboarding.api.ts | onboarding.real.api.ts | Onboarding flow state | Onboarding |
| Sharing | sharingApi | sharing.api.ts | sharing.real.api.ts | Cross-community sharing | Communities |
| Admin | adminApi | admin.api.ts | admin.real.api.ts | User mgmt, flagged content, audit log | Admin |
| Dealer's Choice | dealersChoiceApi | dealersChoice.api.ts | dealersChoice.real.api.ts | DC game rounds, card dealing | Explore |
| Wallet | walletApi | wallet.api.ts | wallet.real.api.ts | Wallet balance, transactions, payouts | Vendor |
| Series | realSeriesApi | (none) | series.real.api.ts | Challenge series (real API only) | Challenges |
| Location | locationApi | location.api.ts | (none) | Reverse geocoding, address autocomplete, place details (mock only) | Explore |
Additionally exported:
seedDatabase()/resetDatabase()-- seed or reset the mock databasemockDb-- direct access to the mock database singletonhttp/httpClient-- axios instance for real API calls
How the Mock Database Works
The mock database (src/api/mockDb.ts) is a MockDatabase class that uses localStorage as its persistence layer. It organizes data into named collections (similar to database tables).
Collections
The mock DB manages 65+ collections including:
users, sessions, challenges, categories, userChallenges, trackRecords, trackRecordEntries, communities, communityMemberships, communityFeed, gifts, schools, classes, notifications, savedList, badges, userBadges, xpEvents, learningPaths, portfolios, communityGoals, auditLogs, flaggedContent, dealersChoiceGames, walletTransactions, payouts, and many more.
Operations
typescript
// CRUD operations
mockDb.getAll<T>(collection) // Get all items
mockDb.findById<T>(collection, id) // Find by ID
mockDb.findWhere<T>(collection, predicate) // Find by predicate
mockDb.findOne<T>(collection, predicate) // Find first match
mockDb.insert<T>(collection, data) // Insert (auto ID + timestamps)
mockDb.insertWithId<T>(collection, data) // Insert with specific ID (seeding)
mockDb.update<T>(collection, id, data) // Update by ID
mockDb.delete(collection, id) // Delete by ID
mockDb.clear(collection) // Clear a collection
mockDb.clearAll() // Clear everything
mockDb.count(collection, predicate?) // Count itemsStorage
Each collection is stored in localStorage under the key docurious-v1-{collectionName}. Data is serialized as JSON objects keyed by entity ID.
Seeding
On first load, seed.ts populates the database with demo data: 9 user accounts (one per role), sample challenges across multiple categories, communities, badges, notifications, and all other supporting data. The seed check uses a docurious-seeded flag in localStorage.
How to Add a New API Module
Define types in
src/types/myDomain.types.tsand re-export fromsrc/types/index.ts.Create mock implementation at
src/api/myDomain.api.ts:
typescript
import { mockDb, generateId, timestamp } from './mockDb'
import { success, error, paginated, withDelay } from './client'
import type { MyEntity, ApiResponse, PaginatedResponse } from '../types'
export const myDomainApi = {
async getAll(): Promise<PaginatedResponse<MyEntity>> {
return withDelay(() => {
const items = mockDb.getAll<MyEntity>('myEntities')
return paginated(items)
})
},
async getById(id: string): Promise<ApiResponse<MyEntity>> {
return withDelay(() => {
const item = mockDb.findById<MyEntity>('myEntities', id)
if (!item) return error('Not found')
return success(item)
})
},
async create(data: Omit<MyEntity, 'id' | 'createdAt' | 'updatedAt'>): Promise<ApiResponse<MyEntity>> {
return withDelay(() => {
const item = mockDb.insert<MyEntity>('myEntities', data)
return success(item)
})
},
}- Create real implementation at
src/api/myDomain.real.api.ts:
typescript
import { http } from './httpClient'
import type { MyEntity, ApiResponse } from '../types'
export const realMyDomainApi = {
async getAll(): Promise<ApiResponse<MyEntity[]>> {
const response = await http.get('/my-domain')
return { success: true, data: response.data }
},
// ...
}- Register in
src/api/index.ts:
typescript
import { myDomainApi as mockMyDomainApi } from './myDomain.api'
import { realMyDomainApi } from './myDomain.real.api'
export const myDomainApi = apiConfig.useMockApi ? mockMyDomainApi : realMyDomainApiAdd collection to mockDb if needed -- add the collection name to the
CollectionNametype and theloadFromStoragelist inmockDb.ts.Add seed data in
src/api/seed.tsto populate the collection on first load.
Connecting to the Real Backend
When VITE_USE_MOCK_API is set to 'false', the *.real.api.ts modules take over. They use an axios instance (src/api/httpClient.ts) configured with:
- Base URL from
VITE_API_URL(default:http://localhost:8000/api/portal) - Auth header injection -- automatically attaches
Authorization: Bearer <token>from the auth store - Token refresh interceptor -- on 401 responses, attempts to refresh the access token before retrying
The real API files call the Express backend, which runs 130+ endpoints across 20 modules. Responses from the backend already match the ApiResponse<T> / PaginatedResponse<T> format, so the real API files are generally thin wrappers.
For endpoints serving legacy production data (challenges, vendors, users from the original database), the Adapter Layer transforms the response to match frontend types.
See Backend Endpoints for the complete endpoint reference, or Backend Quick Start to get the server running.