Aurabase Logo
aurabasedocs
docs›Guides›Expo / React Native

Use Aurabase in an Expo / React Native app

Create the client with EXPO_PUBLIC_* variables, keep the session across app restarts, sign users in with a provider through a deep link, and subscribe to database changes.

5 min read·Level intermediate·Revised on Oct 22, 2026
This English text was generated automatically from the French original and has not been reviewed yet.
#
Status

1. What was verified

@aurabase/aurabase-js bundles without error for Android in an Expo project (npx expo export --platform android, Expo SDK 58, React Native 0.88). Running the app on a device or a simulator has not been verified yet: treat the session and realtime examples below as the intended usage, and report anything that differs.

Aurabase has no Flutter or Dart package on pub.dev yet: for Flutter, call the REST API (see REST API).

#
Setup

2. Install and configure

terminalbash
npx expo install expo-secure-store
npm install @aurabase/aurabase-js

Expo exposes to the app only the variables whose name starts with EXPO_PUBLIC_. Put the project URL and the anon key (never the service_role key: it would ship inside the app) in .env:

.envbash
EXPO_PUBLIC_AURABASE_URL=https://<slug>.aurabase.cloud
EXPO_PUBLIC_AURABASE_ANON_KEY=aura_anon_…
#
Session

3. Keep the session

On the web the SDK keeps the session in localStorage; React Native has none. Give the client an auth store that writes the session to expo-secure-store. The store is read at start-up, before the client is created:

lib/aurabase.tstypescript
import * as SecureStore from 'expo-secure-store'
import { BaseAuthStore, createClient } from '@aurabase/aurabase-js'
import type { User } from '@aurabase/aurabase-js'

const KEY = 'aurabase-session'

class SecureStoreAuthStore extends BaseAuthStore {
  override save(token: string, refreshToken: string, user: User): void {
    super.save(token, refreshToken, user)
    void SecureStore.setItemAsync(KEY, JSON.stringify({ token, refreshToken, user }))
  }

  override clear(): void {
    super.clear()
    void SecureStore.deleteItemAsync(KEY)
  }
}

/** Read the saved session once, at app start, before rendering anything that needs it. */
export async function createAuraClient() {
  const store = new SecureStoreAuthStore()
  const saved = await SecureStore.getItemAsync(KEY)
  if (saved) {
    const { token, refreshToken, user } = JSON.parse(saved) as {
      token: string
      refreshToken: string
      user: User
    }
    store.save(token, refreshToken, user)
  }
  return createClient(
    process.env.EXPO_PUBLIC_AURABASE_URL!,
    process.env.EXPO_PUBLIC_AURABASE_ANON_KEY!,
    { auth: { storage: store } },
  )
}
Attention

Keep the stored value small. The code of expo-secure-store does not enforce a size limit, but the platform keystores are not designed for large values: if your session carries a lot of user metadata and save fails on a device, keep only the tokens, or use @react-native-async-storage/async-storage (less protected).

#
OAuth

4. Sign in with a provider

OAuth in a mobile app uses the system browser and comes back to the app through a deep link. Use the PKCE flow: the code that comes back is useless without the secret the app kept.

  1. Declare the app scheme in app.json ("scheme": "myapp"), then add the redirect URL in Studio › Authentication › Configuration › Redirect URLs. In a production build it is myapp://auth-callback; in Expo Go it is the exp://… address that Linking.createURL('auth-callback') prints, so add that one while developing.
  2. Create the client with auth: { storage: store, flowType: 'pkce' } (the createClient call above), install the two Expo modules, then start the flow and exchange the code:
terminalbash
npx expo install expo-web-browser expo-linking
lib/signInWithProvider.tstypescript
import * as Linking from 'expo-linking'
import * as WebBrowser from 'expo-web-browser'
import type { AuraClient } from '@aurabase/aurabase-js'

export async function signInWithProvider(aura: AuraClient, provider: 'google' | 'github') {
  const redirectTo = Linking.createURL('auth-callback')
  // On React Native the SDK does not redirect: it returns the URL to open.
  const { data, error } = await aura.auth.signInWithOAuth(provider, { redirectTo })
  if (error || !data) return { error }

  const result = await WebBrowser.openAuthSessionAsync(data.url, redirectTo)
  if (result.type !== 'success') return { error: new Error('Sign-in cancelled') }

  // The server redirects to redirectTo?code=… ; exchange the code for a session.
  const code = Linking.parse(result.url).queryParams?.code
  if (typeof code !== 'string') return { error: new Error('No code in the redirect') }
  return aura.auth.exchangeCodeForSession(code)
}

The PKCE secret (code_verifier) stays in memory while the system browser is open. If the system kills the app during sign-in, the exchange returns no_code_verifier: start the flow again.

To sign in with the native Google or Apple dialog instead of a browser, get an ID token from the provider's SDK and call aura.auth.signInWithIdToken('google', idToken). The client ID it is checked against is the one configured for the provider in Studio › Authentication › Providers.

#
Realtime

5. Realtime

React Native provides a WebSocket, which the SDK uses. A channel opened before the user signs in is re-authenticated when the session changes, so the order of subscribe() and signInWithPassword() does not matter.

components/Todos.tsxtypescript
import { useEffect } from 'react'
import type { AuraClient } from '@aurabase/aurabase-js'

export function useTodoChanges(aura: AuraClient, onChange: (type: string) => void) {
  useEffect(() => {
    const channel = aura.realtime
      .channel('todos')
      .on('postgres_changes', { event: '*', schema: 'public', table: 'todos' }, (change) =>
        onChange(change.eventType),
      )
      .subscribe((status) => {
        if (status === 'CHANNEL_ERROR') console.warn('realtime refused')
      })
    return () => {
      channel.unsubscribe()
    }
  }, [aura, onChange])
}
Last updated · Oct 22, 2026