Skip to content

Instantly share code, notes, and snippets.

@KaBankz
Last active November 23, 2024 21:33
Show Gist options
  • Save KaBankz/fc90def87c11d80aace0e1006d6e04e4 to your computer and use it in GitHub Desktop.
Save KaBankz/fc90def87c11d80aace0e1006d6e04e4 to your computer and use it in GitHub Desktop.

Expo Env Helper Example

Note

I just threw this together in a couple minutes, so it's not perfect. The implementation probably has a lot of flaws and could be improved.

But I think I got my point across, if not let me know and I can try to explain it better.

.env*:

EXPO_PUBLIC_API_URL=https://jsonplaceholder.typicode.com
EXPO_PUBLIC_FEATURE_FLAG=true

SENTRY_AUTH_TOKEN=123456
GOOGLE_SERVICES_JSON=./google-services.json

On app launch (or build) the env helper should parse the .env* file and expose the public environment variables to the app via the env helper.

Example Use Case:

import { env } from '@expo/env';

...

return (
  <View>
    ...
    {env.EXPO_PUBLIC_FEATURE_FLAG
      ? <Text>Feature is enabled</Text>
      : <Text>Feature is disabled</Text>}
    ...
  </View>
)

Example Invalid Use Case:

import { env } from '@expo/env';

...

return(
  <View>
    ...
    <Text>
      My Sentry Auth Token is: {env.SENTRY_AUTH_TOKEN} ๐Ÿ˜ˆ
    </Text>
    ...
  </View>
)

Possible Env Config:

import { env } from '@expo/env';
import { z } from 'zod';

env.config({
  /** Backend API URL */
  EXPO_PUBLIC_API_URL: z.string().url(),
  /** Main app domain */
  EXPO_PUBLIC_DOMAIN: z.string().url(),
  /** Feature flag */
  EXPO_PUBLIC_FEATURE_FLAG: z.boolean().optional(),
  /** Google Services JSON */
  GOOGLE_SERVICES_JSON: z.string(),
});

The env helper should only give access to environment variables prefixed with EXPO_PUBLIC_, all other variables should throw an error if accessed.

Benefits

  • You now know 100% that the environment variable you are trying to access exists, no more wondering why your app isn't working because you misspelled process.env.EXPO_PUBLIC_APII_URL ๐ŸŽ‰.

  • Typesafety, with the env helper you get types for all your environment variables, no more process.env.EXPO_PUBLIC_API_URL as string

  • You can also add validation to the env helper, to make sure that the values of the environment variables are of the correct format

Current Solutions

The way I currently handle this is by setting up my own env helper, which is a bit of a pain to maintain and setup for each project.

Warning

This example may not be valid, since I had to cut out a lot of the code to remove unnecessary parts and redact sensitive information.

import Constants from 'expo-constants';
import { z } from 'zod';

// Environment variables that are only available at build time.
const buildTimeEnvVars = z
  .object({
    /** The current app variant */
    APP_VARIANT: z.enum(['development', 'beta', 'production']),
    /** Google Services file for iOS */
    GOOGLE_SERVICES_IOS: z.string(),
    /** Google Services file for Android */
    GOOGLE_SERVICES_ANDROID: z.string(),
  })
  .partial();

// Public environment variables that are included in the app bundle.
// These must be prefixed with `EXPO_PUBLIC_`
const runtimeEnvVars = createPrefixedSchema('EXPO_PUBLIC_', {
  /** Backend API URL */
  EXPO_PUBLIC_API_URL: z.string().url(),
  /** Main app domain */
  EXPO_PUBLIC_DOMAIN: z.string().url(),
  /** Google OAuth Web Client ID */
  EXPO_PUBLIC_WEB_CLIENT_ID: z.string(),
  /** RevenueCat Public API Key for iOS */
  EXPO_PUBLIC_PURCHASES_API_KEY_IOS: z.string(),
  /** RevenueCat Public API Key for Android */
  EXPO_PUBLIC_PURCHASES_API_KEY_ANDROID: z.string(),
});

// Merge the build and runtime environment variables into a single schema
// to provide autocompletion for all environment variables
const envVariables = buildTimeEnvVars.merge(runtimeEnvVars);

//* NOTE: This does not guarantee typesafety. All this does is provide autocompletion to `process.env`
declare global {
  namespace NodeJS {
    interface ProcessEnv extends z.infer<typeof envVariables> {}
  }
}

// Extract the APP_VARIANT from the Expo config and validate it
const APP_VARIANT = z
  .enum(['development', 'beta', 'production'])
  .parse(Constants.expoConfig?.extra?.APP_VARIANT);

// Since we don't have access to the `process.env` object in expo, we have to manually map the environment variables so they get inlined by babel.
const processEnvMap = {
  EXPO_PUBLIC_API_URL: process.env.EXPO_PUBLIC_API_URL,
  EXPO_PUBLIC_DOMAIN: process.env.EXPO_PUBLIC_DOMAIN,
  EXPO_PUBLIC_WEB_CLIENT_ID: process.env.EXPO_PUBLIC_WEB_CLIENT_ID,
  EXPO_PUBLIC_PURCHASES_API_KEY_IOS:
    process.env.EXPO_PUBLIC_PURCHASES_API_KEY_IOS,
  EXPO_PUBLIC_PURCHASES_API_KEY_ANDROID:
    process.env.EXPO_PUBLIC_PURCHASES_API_KEY_ANDROID,
} as const;

export const env = {
  APP_VARIANT,
  ...runtimeEnvVars.parse(processEnvMap),
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment