Configuring environment variables correctly is crucial for maintaining the security and functionality of your React Native application. In this step-by-step guide, we'll walk you through validating environment variables in React Native using the react-native-config library. Following these instructions ensures that your app's environment files contain all the necessary variables and files
Let's roll up our sleeves and start coding
Step 1: Install the required dependencies
yarn add react-native-config zod
npm i react-native-config zodStep 2: Define your environment variables with Zod
Start by defining your environment variables using the Zod library. Create a file, let’s say env.ts, and add the following code:
import z from 'zod';
const NativeConfig = z.object({
API_URL: z.string(),
BASE_URL: z.string(),
});
export type NativeConfigType = z.infer<typeof NativeConfig>;
export default NativeConfig;
Then, we’ll need to create another file called react-native-config.d.ts to have autocompletion and type-safety using Typescript for our variables. Placed this file at your project’s root.
import { NativeConfigType } from './env';
declare module 'react-native-config' {
export interface NativeConfig extends NativeConfigType {}
export const Config: NativeConfig;
export default Config;
}
Step 3: Implement the Validation Function
Let’s break down the implementation into three key parts:
First, we need to create a file that contains the validation function, let’s call it validate-env.ts, now we import fs and path node utilities to read the env file. If the file doesn’t exist we throw an error indicating that.
/* validate_env.ts */
import fs from 'fs';
import path from 'path';
import Environment from '../src/@types/env';
const APP_ENV = process.env.APP_ENV ?? 'dev';
const isDev = APP_ENV === 'dev';
const envFile = path.join(__dirname, isDev ? '../.env' : `../.env.${APP_ENV}`);
(() => {
const fileExists = fs.existsSync(envFile);
if (!fileExists) {
console.error(
`❌ Missing .env.${APP_ENV} file. Make sure you have .env.${APP_ENV} file in the root directory.`,
);
throw new Error(`Missing .env.${APP_ENV} file. Check terminal for more details`);
}
const content = fs.readFileSync(envFile, 'utf8');
const envVars = content.split('\n');
const jsonObject: { [key: string]: string } = {};
envVars.forEach((envVar: string) => {
if (envVar === '') {
return;
}
const [key, value] = envVar.split('=');
jsonObject[key] = value;
});
const parsed = Environment.safeParse(jsonObject);
if (!parsed.success) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors,
`\n❌ Missing variables in .env.${APP_ENV} file, Make sure all required variables are defined in the .env.${APP_ENV} file.`,
);
throw new Error('Invalid environment variables, Check terminal for more details ');
}
})();
export default {};
/* validate_env_1.ts */
import fs from 'fs';
import path from 'path';
import NativeConfig from './env';
const APP_ENV = process.env.APP_ENV ?? 'dev';
const isDev = APP_ENV === 'dev';
const envFile = path.join(__dirname, isDev ? '../.env' : `../.env.${APP_ENV}`);
(() => {
const fileExists = fs.existsSync(envFile);
if (!fileExists) {
console.error(
`❌ Missing .env.${APP_ENV} file. Make sure you have .env.${APP_ENV} file in the root directory.`,
);
throw new Error(`Missing .env.${APP_ENV} file. Check terminal for more details`);
}
// ...
})();
export default {};
/* validate_env_2.ts */
// ... prev code
const content = fs.readFileSync(envFile, 'utf8');
const envVars = content.split('\n');
const jsonObject: { [key: string]: string } = {};
envVars.forEach((envVar: string) => {
if (envVar === '') {
return;
}
const [key, value] = envVar.split('=');
jsonObject[key] = value;
});
/* validate_env_3.ts */
const parsed = NativeConfig.safeParse(jsonObject);
if (!parsed.success) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors,
`\n❌ Missing variables in .env.${APP_ENV} file, Make sure all required variables are defined in the .env.${APP_ENV} file.`,
);
throw new Error('Invalid environment variables, Check terminal for more details ');
}Then, if the file exists we read the content and stored it in a JSON object.
/* validate_env.ts */
import fs from 'fs';
import path from 'path';
import Environment from '../src/@types/env';
const APP_ENV = process.env.APP_ENV ?? 'dev';
const isDev = APP_ENV === 'dev';
const envFile = path.join(__dirname, isDev ? '../.env' : `../.env.${APP_ENV}`);
(() => {
const fileExists = fs.existsSync(envFile);
if (!fileExists) {
console.error(
`❌ Missing .env.${APP_ENV} file. Make sure you have .env.${APP_ENV} file in the root directory.`,
);
throw new Error(`Missing .env.${APP_ENV} file. Check terminal for more details`);
}
const content = fs.readFileSync(envFile, 'utf8');
const envVars = content.split('\n');
const jsonObject: { [key: string]: string } = {};
envVars.forEach((envVar: string) => {
if (envVar === '') {
return;
}
const [key, value] = envVar.split('=');
jsonObject[key] = value;
});
const parsed = Environment.safeParse(jsonObject);
if (!parsed.success) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors,
`\n❌ Missing variables in .env.${APP_ENV} file, Make sure all required variables are defined in the .env.${APP_ENV} file.`,
);
throw new Error('Invalid environment variables, Check terminal for more details ');
}
})();
export default {};
/* validate_env_1.ts */
import fs from 'fs';
import path from 'path';
import NativeConfig from './env';
const APP_ENV = process.env.APP_ENV ?? 'dev';
const isDev = APP_ENV === 'dev';
const envFile = path.join(__dirname, isDev ? '../.env' : `../.env.${APP_ENV}`);
(() => {
const fileExists = fs.existsSync(envFile);
if (!fileExists) {
console.error(
`❌ Missing .env.${APP_ENV} file. Make sure you have .env.${APP_ENV} file in the root directory.`,
);
throw new Error(`Missing .env.${APP_ENV} file. Check terminal for more details`);
}
// ...
})();
export default {};
/* validate_env_2.ts */
// ... prev code
const content = fs.readFileSync(envFile, 'utf8');
const envVars = content.split('\n');
const jsonObject: { [key: string]: string } = {};
envVars.forEach((envVar: string) => {
if (envVar === '') {
return;
}
const [key, value] = envVar.split('=');
jsonObject[key] = value;
});
/* validate_env_3.ts */
const parsed = NativeConfig.safeParse(jsonObject);
if (!parsed.success) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors,
`\n❌ Missing variables in .env.${APP_ENV} file, Make sure all required variables are defined in the .env.${APP_ENV} file.`,
);
throw new Error('Invalid environment variables, Check terminal for more details ');
}Finally, we use Zod’s safeParse method from the Zod’s object NativeConfig we created before, this will allow us to compare if the jsonObject contains all the variables previously defined without throwing an error, then we check if the parsed.success is false to throw our error parsing errors to see them in the terminal.
/* validate_env.ts */
import fs from 'fs';
import path from 'path';
import Environment from '../src/@types/env';
const APP_ENV = process.env.APP_ENV ?? 'dev';
const isDev = APP_ENV === 'dev';
const envFile = path.join(__dirname, isDev ? '../.env' : `../.env.${APP_ENV}`);
(() => {
const fileExists = fs.existsSync(envFile);
if (!fileExists) {
console.error(
`❌ Missing .env.${APP_ENV} file. Make sure you have .env.${APP_ENV} file in the root directory.`,
);
throw new Error(`Missing .env.${APP_ENV} file. Check terminal for more details`);
}
const content = fs.readFileSync(envFile, 'utf8');
const envVars = content.split('\n');
const jsonObject: { [key: string]: string } = {};
envVars.forEach((envVar: string) => {
if (envVar === '') {
return;
}
const [key, value] = envVar.split('=');
jsonObject[key] = value;
});
const parsed = Environment.safeParse(jsonObject);
if (!parsed.success) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors,
`\n❌ Missing variables in .env.${APP_ENV} file, Make sure all required variables are defined in the .env.${APP_ENV} file.`,
);
throw new Error('Invalid environment variables, Check terminal for more details ');
}
})();
export default {};
/* validate_env_1.ts */
import fs from 'fs';
import path from 'path';
import NativeConfig from './env';
const APP_ENV = process.env.APP_ENV ?? 'dev';
const isDev = APP_ENV === 'dev';
const envFile = path.join(__dirname, isDev ? '../.env' : `../.env.${APP_ENV}`);
(() => {
const fileExists = fs.existsSync(envFile);
if (!fileExists) {
console.error(
`❌ Missing .env.${APP_ENV} file. Make sure you have .env.${APP_ENV} file in the root directory.`,
);
throw new Error(`Missing .env.${APP_ENV} file. Check terminal for more details`);
}
// ...
})();
export default {};
/* validate_env_2.ts */
// ... prev code
const content = fs.readFileSync(envFile, 'utf8');
const envVars = content.split('\n');
const jsonObject: { [key: string]: string } = {};
envVars.forEach((envVar: string) => {
if (envVar === '') {
return;
}
const [key, value] = envVar.split('=');
jsonObject[key] = value;
});
/* validate_env_3.ts */
const parsed = NativeConfig.safeParse(jsonObject);
if (!parsed.success) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors,
`\n❌ Missing variables in .env.${APP_ENV} file, Make sure all required variables are defined in the .env.${APP_ENV} file.`,
);
throw new Error('Invalid environment variables, Check terminal for more details ');
}You can see the full example of the validate-env.ts file here.
Step 4: Set up the validation process
We are almost there, we need one more dependency to run our validation function. We added ts-node as a dev dependency.
yarn add -D ts-node
npm install ts-node --save-devIt is time to modify our package.json to update our build scripts to run the validate-env.ts file before building our application.
The goal of adding APP_ENV=environment and ts-node to each build script is to run the validation of the environment file to avoid building an application with missing variables or no variables at all.
"scripts": {
"ios:staging": "APP_ENV=staging ts-node scripts/validate-env.ts && react-native run-ios --scheme MyReactNativeApp-Staging",
"ios:prod": "APP_ENV=prod ts-node scripts/validate-env.ts && react-native run-ios --scheme MyReactNativeApp",
"android:dev": "APP_ENV=dev ts-node scripts/validate-env.ts && react-native run-android --variant=devDebug --appIdSuffix=dev",
"android:qa": "APP_ENV=qa ts-node scripts/validate-env.ts && react-native run-android --variant=qaDebug --appIdSuffix=qa",
// ... other scripts
}APP_ENV will be used in the validate-env.ts file to read the environment variables from the env file. Your implementation could be slightly different from this just, keep in mind that the value of the APP_ENV should be any of the suffixes you used for your files, for example:
.env.production - APP_ENV=production
.env.stg - APP_ENV=stg
Final Thoughts
React Native Config is a great library that enables smooth reading of environment variables in JavaScript and Native code. It also supports TypeScript. By following these simple steps, you can now quickly detect any missing variables or files during the build process and ensure they stay in sync with other variables added by other team members.
Happy coding!