Skip to main content
Every Bloom app is a monorepo containing an Expo frontend and a Convex backend. This architecture gives you a cross-platform mobile app (iOS, Android, and web) with a real-time serverless backend, all in one codebase.

Directory Overview

your-app/
├── .env                        # Environment variables
├── package.json                # Workspace configuration
├── turbo.json                  # Task runner configuration

├── apps/
│   └── default/                # Expo app (iOS, Android, Web)
│       ├── app/                # App screens and layouts
│       │   ├── _layout.tsx     # Root layout with providers
│       │   └── index.tsx       # Main entry screen
│       ├── lib/                # Shared utilities
│       │   └── auth-client.ts  # Authentication client
│       ├── app.json            # Expo configuration
│       └── package.json        # App dependencies

└── packages/
    └── backend/                # Convex backend
        └── convex/
            ├── schema.ts       # Database schema
            ├── functions.ts    # Auth wrapper functions
            ├── auth.ts         # Authentication setup
            ├── http.ts         # HTTP endpoints
            └── _generated/     # Auto-generated types

Root Files

The root of your project contains configuration files that tie everything together.
Defines your monorepo workspace structure using Bun workspaces. It includes scripts to run your app and backend together.
{
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "dev": "turbo dev",
    "dev:backend": "turbo dev --filter=@bloom/backend",
    "dev:mobile": "turbo dev --filter=@bloom/mobile"
  }
}
Run bun run dev to start both the frontend and backend simultaneously.
Configures Turborepo, which orchestrates tasks across your monorepo. It defines how dev, build, lint, and typecheck commands run across packages.
Stores environment variables shared across your app. Required variables:
  • CONVEX_DEPLOYMENT — Your Convex deployment identifier
  • EXPO_PUBLIC_CONVEX_URL — The Convex cloud URL for your app
  • EXPO_PUBLIC_CONVEX_SITE_URL — The URL for Convex HTTP actions (used for auth)
Never commit .env files to version control. Use .env.example as a template.

Frontend: apps/default/

Your Expo app lives here. It’s a universal React Native app that runs on iOS, Android, and web from a single codebase.

App Directory

The app/ folder uses Expo Router for file-based routing. Each file becomes a route.
The root layout wraps your entire app with required providers:
export default function RootLayout() {
  return (
    <ConvexProvider client={convex}>
      <ConvexBetterAuthProvider client={authClient}>
        <Stack screenOptions={{ headerShown: false }} />
      </ConvexBetterAuthProvider>
    </ConvexProvider>
  );
}
  • ConvexProvider — Connects your app to the Convex backend
  • ConvexBetterAuthProvider — Manages authentication state
  • Stack — The navigation container for your screens

Key Configuration Files

Expo configuration file that defines your app’s name, icons, splash screen, and platform-specific settings.
{
  "expo": {
    "name": "Your App Name",
    "slug": "your-app",
    "newArchEnabled": true,
    "plugins": [
      "expo-router",
      "expo-splash-screen",
      "expo-secure-store"
    ]
  }
}
Configures the Better Auth client for authentication. Handles platform-specific storage (secure store on native, cookies on web) and cross-domain authentication.
export const authClient = createAuthClient({
  baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
  plugins: [
    expoClient({
      storage: expoSecureStore,
    }),
    crossDomainClient(),
  ],
});
TypeScript configuration with path aliases for cleaner imports:
  • @/* — Maps to the current directory
  • @/convex/* — Maps to ../../packages/backend/convex/*
This lets you import backend types directly:
import { api } from "@/convex/_generated/api";

Backend: packages/backend/convex/

Your Convex backend contains your database schema, serverless functions, and authentication logic.

Core Files

Defines your database tables and their structure. Convex uses this to generate type-safe queries.
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  posts: defineTable({
    title: v.string(),
    content: v.string(),
    authorId: v.string(),
  }),
});
Changes to your schema automatically sync with your database.

Generated Files

The _generated/ folder contains auto-generated TypeScript types. These are created by Convex and should not be edited manually.
FilePurpose
api.d.tsType-safe references to your functions
dataModel.d.tsTypes matching your database schema
server.d.tsServer-side types for queries and mutations
These files update automatically when you modify your schema or functions. They enable full type safety between your frontend and backend.

Key Patterns

Environment Variables

Environment variables flow through your app in a specific way:
  1. Defined in .env at the project root
  2. Backend reads them via the --env-file flag
  3. Frontend loads them through Metro bundler using @expo/env
  4. Variables prefixed with EXPO_PUBLIC_ are available in frontend code

Type-Safe Imports

The path alias @/convex/* lets your frontend import backend types directly:
import { api } from "@/convex/_generated/api";
import { useQuery } from "convex/react";

function PostList() {
  const posts = useQuery(api.posts.list);
  // posts is fully typed based on your schema
}

Authentication Flow

Authentication is pre-wired between frontend and backend:
  1. Frontend uses authClient from lib/auth-client.ts
  2. Auth requests route through Convex HTTP endpoints
  3. Session state syncs via ConvexBetterAuthProvider
  4. Backend functions access the user via ctx.session

What’s Next?