Skip to main content

Overview

Aura Farm is a mobile application built across three primary layers: a React Native (Expo) frontend, an Express/Node.js backend API, and a PostgreSQL database managed by Prisma. Supabase provides authentication and storage services that both layers consume.
┌─────────────────────────────┐
│  React Native (Expo)        │  ← Frontend (iOS / Android)
│  Expo Router · NativeWind   │
└────────────┬────────────────┘
             │ HTTPS / Bearer JWT
┌────────────▼────────────────┐
│  Express 5 API              │  ← Backend (Node.js)
│  Prisma ORM · Zod · Winston │
└────────────┬────────────────┘

     ┌───────┴────────┐
     │                │
┌────▼────┐   ┌───────▼──────┐
│PostgreSQL│   │  Supabase    │
│(Prisma) │   │  Auth + S3   │
└─────────┘   └──────────────┘

Backend structure

The backend lives in backend/src/ and is organized into the following directories:
DirectoryPurpose
routes/Express route definitions — one file per resource
controllers/Request handlers; business logic per route
middleware/Auth, rate limiting, error handling, request logging
types/Shared TypeScript type definitions
config/Swagger/OpenAPI setup
utils/Shared utilities (e.g., Winston logger)
prisma/Prisma schema, migrations, and seed script

Routes

The server mounts seven route groups under /api:
Mount pathFile
/api/authauth.routes.ts
/api/challengeschallenges.routes.ts
/api/completionscompletions.routes.ts
/api/flagsflags.routes.ts
/api/usersusers.routes.ts
/api/leaderboardleaderboard.routes.ts
/api/uploadupload.routes.ts
A /health endpoint returns a liveness check with a timestamp. Interactive API documentation is served at /api/docs via Swagger UI.

Middleware

authenticate — Extracts the Bearer token from the Authorization header, verifies it with Supabase, then looks up the matching user in PostgreSQL. Attaches req.user (id, email, role, supabaseId) for downstream handlers. requireAdmin — Must follow authenticate. Returns 403 if req.user.role is not admin. rateLimiter — Four configurable limiters (public, auth, completion, flag) backed by express-rate-limit. Keyed by IP by default; set RATE_LIMIT_BY=user to key by user ID instead. errorHandler / notFoundHandler — Global error handling and 404 responses. requestLogger — Logs every incoming request via Winston.

Frontend structure

The frontend lives in frontend/ and is organized as an Expo Router application:
DirectoryPurpose
app/File-system routes (Expo Router) — tabs, screens, modals
components/Shared React Native UI components
lib/API client, auth helpers, storage utilities
stores/Zustand global state stores

Key frontend libraries

LibraryRole
Expo RouterFile-system based navigation
NativeWindTailwind CSS utility classes for React Native
TanStack QueryServer-state fetching, caching, and synchronization
ZustandLightweight client-side global state
Supabase JSAuth token management and direct storage access
AxiosHTTP requests (supplementing fetch in some paths)

API base URL auto-detection

In development, the frontend does not need a manually configured API URL. lib/api.ts reads the Metro bundler’s hostUri at runtime:
export function apiBaseUrl(): string {
  const envUrl = process.env.EXPO_PUBLIC_API_URL ?? extra?.apiUrl;
  if (envUrl) return envUrl;

  const hostUri =
    Constants.expoConfig?.hostUri ??
    Constants.manifest2?.extra?.expoGo?.debuggerHost ??
    Constants.manifest?.debuggerHost;

  if (hostUri) {
    const host = hostUri.split(":")[0];
    return `http://${host}:3000`;
  }
  return "http://localhost:3000";
}
This means physical devices on the same WiFi network reach the backend automatically — no .env file required.

Authentication flow

1

User signs in

The frontend calls POST /api/auth/login. The backend delegates to Supabase Auth and returns a JWT access token and refresh token.
2

Session is stored

lib/auth.ts persists the session to AsyncStorage and syncs the tokens to the Supabase JS client so direct storage uploads share the same credentials.
3

Requests are authorized

All authenticated API calls include Authorization: Bearer <token>. If the token is within 30 seconds of expiry, getValidSession() refreshes it before the request is sent.
4

Backend verifies the token

The authenticate middleware calls supabase.auth.getUser(token). On success it queries PostgreSQL for the user record and attaches it to req.user.
5

Role-based access

Admin-only endpoints chain requireAdmin after authenticate, returning 403 if the user’s role is not admin.

Key dependencies

Backend

PackageVersionPurpose
express5.xHTTP server
@prisma/client6.xPostgreSQL ORM
@supabase/supabase-js2.xAuth token verification
zod4.xRequest validation
winston3.xStructured logging
express-rate-limit8.xRate limiting
swagger-ui-express5.xAPI documentation UI

Frontend

PackageVersionPurpose
expo54.xReact Native runtime and toolchain
expo-router6.xFile-system navigation
nativewind4.xTailwind styling for React Native
@tanstack/react-query5.xData fetching and caching
zustand5.xGlobal state management
@supabase/supabase-js2.xAuth and storage client

Environment setup

Configure environment variables and run the app locally.

Database

Explore the Prisma schema and data models.

Build docs developers (and LLMs) love