While doing my normal googling to figure out how other people were managing their config in their react native projects, I came across two seemingly popular methods that I unfortunately just did not click with. So I ended up creating my own workflow for managing and changing between environments.
Firstly I discovered react-native-config, it seemed great and boasted a 12-factor approach, however it just seemed a bit complex for what I was looking for (called me lazy) I didn't want configure something in 2 different way for both iOS and Android.
Secondly appeared react-native-dotenv which seemed much easier to set up and get going, so thats exactly what I did... and thats where it went wrong. I was trying to test changing the API endpoint used in the build, and thats when I discovered that after you make a change to the desired variable, you must also make a change to the file in your codebase which imports this variable for the affect to take place. This was something I was simply not prepared to live with!
So I thought about what I wanted to achieve in my quest for config management, cross platform, simplicity, and ability to configure any number of environments... and an added bonus of security if needed.
JSON would be used to describe an environment using key value notations, while SOPS would be used to encrypt and decrypt sensitive config values which can be safely pushed to git. Example json config:
{
"API_KEY": "KgGLWnjW5qhW63YaTbe9"
"API_URL": "http://api.dev.cartelwars.co",
}
Create a centralised config
module within the app which imports the JSON config file, and export the env vars which can be within the app at any point. The reason we create this config module, instead of importing the config file directly, is so the we have 1 single entry point for the variables where we can manage setting defaults.
Example config.js
:
import ENV from './environment.json';
API_KEY = ENV.API_KEY
API_URL = ENV.API_URL || localhost:3000 // setting default value
export default {
API_KEY,
API_URL
};
Create a directory structure to separate your different environment files, and store your secret values in a SOPS encrypted file. Take the following example directory structure.
my_project/
├── Environments/
│ ├── dev/
| | ├── secrets.json
| | └── values.json
│ ├── stage/
│ └── prod/
└── src/
├── app.js
├── config.js
└── environment.json
Glue it all together with a script which will firstly decrypt the secrets.yaml
into plain text, then join the contents of secrets.yaml
and values.yaml
together into a single JSON payload and insert it into the src/environment.json
file where the config.js
module can import the values for a build. I created a new directory and file from the root of the project my_project/scripts/set-env.sh
. I opted to use Bash for the language as its simple and portable.
Example command to set environment vars (it takes one argument of the env name):
./scripts/set-env.sh dev
Example set-env.sh
SCRIPT_PATH=$0
ENV=$1
CONFIG_PATH="$(dirname $(dirname $0) )/src/environment.json"
ENVIRONMENTS_PATH="$(dirname $(dirname $0) )/environments/${ENV}"
# Use SOPS to decrypt the secrets in place
sops -d -i ${ENVIRONMENTS_PATH}/secrets.json
# Use jq to combine all the environment json files into one json file in the config path
jq -s 'reduce .[] as $item ({}; . * $item)' ${ENVIRONMENTS_PATH}/* > ${CONFIG_PATH}
# Use SOPS to re-encrypt the secrets file again
sops -e -i ${ENVIRONMENTS_PATH}/secrets.json
And thats it. Now whenever you want to your environment vars, you just need to create the var files and run the script, from there your app does the rest.
The high level work flow is as follows:
- Create a new environment sub directory containing a
values.json
andsecrets.json
with your non-sensitive and sensitive environment vars. - Run the
set-env.sh
script to load your new environment vars toenvironment.json
- Start your app in your preferred way, probably with yarn
yarn start