Skip to content

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): number

API Module Catalog

Each module is exported from src/api/index.ts with automatic mock/real switching:

ModuleExport NameMock FileReal FilePurposeFeature Guide
AuthauthApiauth.api.tsauth.real.api.tsLogin, register, token management, Google/Apple OAuthAccounts
UseruserApiuser.api.tsuser.real.api.tsProfile CRUD, settings, deletion requestsAccounts
ChallengechallengeApichallenge.api.tschallenge.real.api.tsChallenge CRUD, search, categories, instancesChallenges
Track RecordtrackRecordApitrackRecord.api.tstrackRecord.real.api.tsTR CRUD, entries, media, submissionTrack Records
ExploreexploreApiexplore.api.tsexplore.real.api.tsCurated views, saved list, searchExplore
CommunitycommunityApicommunity.api.tscommunity.real.api.tsCommunity CRUD, feeds, membershipsCommunities
GiftgiftApigift.api.tsgift.real.api.tsSend/receive gifts, redemptionGifting
SchoolschoolApischool.api.tsschool.real.api.tsRoster, classes, assignments, gradesSchool
NotificationnotificationApinotification.api.tsnotification.real.api.tsNotifications, preferences, mark readNotifications
GamificationgamificationApigamification.api.tsgamification.real.api.tsXP, badges, streaks, levelsGamification
Learning PathlearningPathApilearningPath.api.tslearningPath.real.api.tsPath CRUD, enrollment, progressGamification
PortfolioportfolioApiportfolio.api.tsportfolio.real.api.tsPortfolio creation, PDF exportTrack Records
EventeventApievent.api.tsevent.real.api.tsChallenge events, registrationsExplore
Community GoalcommunityGoalApicommunityGoal.api.tscommunityGoal.real.api.tsCooperative goals, contributionsCommunities
PaymentpaymentApipayment.api.tspayment.real.api.tsStripe checkout, payment methodsVendor
Email VerificationemailVerificationApiemailVerification.api.tsemailVerification.real.api.tsEmail verification flowAccounts
InvitationinvitationApiinvitation.api.tsinvitation.real.api.tsChallenge invitationsGifting
ReflectionreflectionApireflection.api.tsreflection.real.api.tsSEL reflection prompts/responsesReflection
OnboardingonboardingApionboarding.api.tsonboarding.real.api.tsOnboarding flow stateOnboarding
SharingsharingApisharing.api.tssharing.real.api.tsCross-community sharingCommunities
AdminadminApiadmin.api.tsadmin.real.api.tsUser mgmt, flagged content, audit logAdmin
Dealer's ChoicedealersChoiceApidealersChoice.api.tsdealersChoice.real.api.tsDC game rounds, card dealingExplore
WalletwalletApiwallet.api.tswallet.real.api.tsWallet balance, transactions, payoutsVendor
SeriesrealSeriesApi(none)series.real.api.tsChallenge series (real API only)Challenges
LocationlocationApilocation.api.ts(none)Reverse geocoding, address autocomplete, place details (mock only)Explore

Additionally exported:

  • seedDatabase() / resetDatabase() -- seed or reset the mock database
  • mockDb -- direct access to the mock database singleton
  • http / 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 items

Storage

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

  1. Define types in src/types/myDomain.types.ts and re-export from src/types/index.ts.

  2. 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)
    })
  },
}
  1. 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 }
  },
  // ...
}
  1. 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 : realMyDomainApi
  1. Add collection to mockDb if needed -- add the collection name to the CollectionName type and the loadFromStorage list in mockDb.ts.

  2. Add seed data in src/api/seed.ts to 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.

DoCurious Platform Documentation