expo-clerk-convex

repository·main·Indexed 21 days ago

https://github.com/flemingvincent/expo-clerk-convex

An Expo starter project (version 1.0.0) demonstrating the integration of Clerk authentication with a Convex backend. It features email/password sign-up with code verification, Expo Router route protection, and a pre-configured ConvexReactClient. The project includes custom hooks `useSignIn` and `useSignUp` to simplify Clerk's multi-step authentication flows within an Expo application.

Tokens
2.9K
Snippets
10
Records
14
Agent score
73%

What's inside expo-clerk-convex

  1. Understand the Authentication Flow

    main

    This project implements an opinionated authentication architecture:

    • Sign-in: Uses Clerk email/password authentication.
    • Sign-up: Creates the account, triggers an email verification code, and completes verification within the app.
    • Route Protection: Access to specific routes is controlled by the Clerk authentication state within the root Expo Router layout.
    • Backend Security: Convex trusts the authenticated Clerk session for backend access. Note that this implementation does not mirror Clerk users into a separate Convex users table.
  2. Install and set up the Expo Clerk Convex starter

    main

    Follow these steps to clone the repository, install dependencies, and initialize the backend services.

    1. Clone the repository:
    git clone https://github.com/FlemingVincent/expo-clerk-convex.git
    cd expo-clerk-convex
    1. Install dependencies using bun:
    bun install
    1. Initialize the Convex backend:
    npx convex dev

    This command creates the convex/ directory, prompts for Convex sign-in, and provides the deployment URL required for the Expo app.

    1. Start the Expo development server:
    npx expo start --clear --reset-cache
    git clone https://github.com/FlemingVincent/expo-clerk-convex.git
    cd expo-clerk-convex
    bun install
    npx convex dev
    npx expo start --clear --reset-cache
  3. Configure Clerk for Expo and Convex

    main

    Before setting up the local environment, you must configure your Clerk application to support the authentication flow used in this project:

    1. Create a Clerk application at dashboard.clerk.com.
    2. Enable email/password authentication.
    3. Configure email verification to use an email code flow for sign-up.
    4. Enable the Convex integration within the Clerk dashboard.
    5. Locate your Clerk Frontend API URL; you will need this as the CLERK_JWT_ISSUER_DOMAIN for your Convex backend.
  4. Configure environment variables for Expo and Convex

    main

    The project requires specific environment variables to link Clerk, Convex, and Expo.

    Expo Environment Variables

    Add these to your .env.local file:

    • EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: Your Clerk publishable key.
    • EXPO_PUBLIC_CONVEX_URL: The deployment URL provided by npx convex dev.

    Convex Environment Variables

    Add this to your Convex dashboard environment variables:

    • CLERK_JWT_ISSUER_DOMAIN: The Clerk Frontend API URL.
  5. Configure the Convex URL environment variable

    main

    The project requires the EXPO_PUBLIC_CONVEX_URL environment variable to be defined in your .env file to initialize the Convex client. If this variable is missing, the application will throw an error: Add EXPO_PUBLIC_CONVEX_URL to your .env file.

    EXPO_PUBLIC_CONVEX_URL=https://your-deployment-name.convex.cloud
  6. Configure Convex authentication with Clerk

    main

    To enable Clerk authentication within your Convex backend, you must define an AuthConfig object in convex/auth.config.ts. This configuration tells Convex which JWT issuer is authorized to authenticate requests.

    Specifically, you must provide a providers array containing an object with:

    • domain: The Clerk JWT issuer domain (e.g., https://your-issuer-domain.clerk.accounts.dev). This should be loaded from the CLERK_JWT_ISSUER_DOMAIN environment variable.
    • applicationID: Set this to "convex" to match the expected application identifier for this integration.
    import { AuthConfig } from "convex/server";
    
    export default {
      providers: [
        {
          domain: process.env.CLERK_JWT_ISSUER_DOMAIN!,
          applicationID: "convex",
        },
      ],
    } satisfies AuthConfig;
  7. Use the useSignUp hook for Clerk authentication flows

    main

    The useSignUp hook provides a simplified interface for managing the multi-step Clerk sign-up process in an Expo application. It abstracts the complexity of calling Clerk's underlying methods for password creation, email verification, and session finalization.

    It provides three main capabilities:

    1. signUp: Initiates the sign-up process with an email and password, and automatically triggers the email verification code delivery.
    2. verifyOtp: Verifies the email code (OTP) provided by the user and, if the status is complete, finalizes the sign-up and navigates the user to the root (/) using expo-router.
    3. isLoaded: A boolean indicating if the Clerk sign-up state is ready to be used (i.e., fetchStatus is idle).

    Note: This hook uses expo-router for navigation upon successful finalization.

    import { useSignUp } from './path-to-your-hooks/useSignUp';
    
    const SignUpScreen = () => {
      const { isLoaded, signUp, verifyOtp } = useSignUp();
    
      const handleSignUp = async () => {
        try {
          await signUp({ email: 'user@example.com', password: 'securePassword123' });
          // Proceed to OTP input screen
        } catch (err) {
          console.error('Sign up failed:', err);
        }
      };
    
      const handleVerify = async (token: string) => {
        try {
          await verifyOtp({ token });
        } catch (err) {
          console.error('Verification failed:', err);
        }
      };
    
      if (!isLoaded) return <LoadingView />;
    
      return (
        // Your UI components
      );
    };
  8. Initialize the Convex client

    main

    The application exports a pre-configured convex instance of ConvexReactClient. This instance is initialized using the EXPO_PUBLIC_CONVEX_URL and has unsavedChangesWarning set to false to prevent blocking UI interactions during development or usage.

    import { convex } from './lib/convex';
    
    // Use the exported convex instance to interact with your backend
    // e.g., useQuery(api.myFunctions.myQuery, {});
  9. Define public queries with query()

    main

    Use query to define functions that read from your Convex database and are accessible from the client. The function receives a QueryCtx as its first argument.

    import { query } from "./_generated/server";
    
    export const myQuery = query({
      args: {},
      handler: async (ctx) => {
        // ctx is QueryCtx
        return await ctx.db.query("tableName").collect();
      },
    });
  10. Define HTTP actions with httpAction()

    main

    Use httpAction to define functions that respond to incoming HTTP requests. These are used when you have routed a specific path and method in convex/http.js. The function receives an ActionCtx as its first argument and a Fetch API Request object as its second argument.

    import { httpAction } from "./_generated/server";
    
    export const myHttpAction = httpAction(async (ctx, request) => {
      // ctx is ActionCtx
      // request is a Fetch API Request object
      return new Response("Hello from HTTP!");
    });
  11. Define public actions with action()

    main

    Use action to define functions that can execute arbitrary JavaScript code, including non-deterministic code or side-effects like calling third-party APIs. Actions cannot interact with the database directly; they must call queries or mutations using the ActionCtx. The function receives an ActionCtx as its first argument.

    import { action } from "./_generated/server";
    
    export const myAction = action({
      args: {},
      handler: async (ctx, args) => {
        // ctx is ActionCtx
        // Call a mutation to interact with the database
        await ctx.runMutation(api.myMutation, { text: "hello" });
      },
    });