In this two-part series I’ll talk about:
- Why to separate your configuration and code
- How to do it “in general”
- How to do it with an Azure App Service
- Some typical solutions for managing environment variables in the browser
- Using environment variables with an Angular app served from an Azure App Service.
Separating Configuration and Code
Experienced developers recognize that a best practice for writing apps is to strictly separate configuration and code. Configuration varies across deployments and environments, while code shouldn’t.
For example, let’s say your app connects to a database. You’ll typically need three things to connect to a SQL database: a hostname, a username, and a password. These three pieces of information are highly likely to vary across environments.
When you develop the app locally, you’ll have these values:
- Hostname: localhost
- Username: sean
- Password: supersecretlocalpassword
When you push to production, they’ll be different:
- Hostname: myproddatabase.database.windows.net
- Username: myappusername
- Password: evenmoresecretprodpassword
If you are writing an Angular app (or any front-end app) you might want to also have a back-end server API. When you’re developing locally, your Angular app might point to localhost:3000, and in a production environment you would point to api.myserver.com.
The list goes on and on of pieces of configuration that will vary across deployments and across environments. So how do people typically separate config and code?
How to Handle Environment-specific Variables (in General)
So how do people typically deal with separating their config and their code? In the “server-side” world, apps will typically use something called, surprisingly enough, environment variables. Every operating system supports environment variables (although using them on Windows is a bit more clunky). Environment variables are treated as a simple key-value store, like a
Dictionary<string, string>. The keys are typically UNDERSCORE_DELIMITED_UPPERCASE, but that’s just a convention.
You can see the environment variables on your Mac or Linux system by opening a terminal and typing
printenv. On Windows, open Powershell and type
Environment variables are completely separate from apps, and they are a perfect place for configuration. Your local machine will define:
and the prod server will have:
and you can deploy the *exact same code* to each machine and have it automatically hook up to the right database. Additionally, every cloud provider (where you would typically deploy your application these days) supports environment variables. Let’s look at how Azure handles them.
How to Handle Environment-specific Variables (in Azure App Services)
I won’t go into too much depth, as there is an excellent, if dated, article on the Azure blog. The basic concept is that Azure App Services let you configure their environment variables through something called “App Settings” and “Connection Strings” (these are familiar to .NET developers). You set an app setting or a connection string in the Azure Portal (or using the Azure CLI, Azure Powershell tools, or through an ARM template), and it’s automatically available to your .NET app through, for example,
What’s cool about it is that they also expose these app settings and connection strings as environment variables. We’ll use this feature in part 2, where I’ll show how to use these Azure environment variables in the browser.
How to Handle Environment-specific Variables (on the front end)
Why do you need environment variables on the front-end?
- With most single page apps (SPAs) and modern websites, you’ll be accessing an API. On localhost, you most likely want Angular to point to localhost:3000, but in prod, you’ll point it to the live hosted API.
- You may enable more features (feature flagging/toggling) on one environment than another
- You want to be able to move your bundled front-end web app between environments easily without changing config or rebuilding
- and more
So how do people tackle this challenge?
There are three main solutions people have used to approach this problem:
- Include the configuration directly in the code. Then you have an
ifstatement somewhere that checks the
window.locationto decide which endpoint to point to. This violates the principle of keeping config and code separate. This is a naive approach that is probably the most popular way to accomplish it.
- Use a build step to inject config into the asset bundle you send to the browser. This is slighly more elegant, and can be easily accomplished if you’re already using a build tool like gulp or grunt (see gulp-ng-config, for example). This would be accomplished during build time and then uploaded to the server as a pre-environment-ized bundle. The code in your source control repository is separated from the config, but the bundle you upload to your server and subsequently download to the browser includes both config and code. However, this doesn’t allow you to build once, deploy anywhere. You’ll have to re-build the app once for each environment.
- Download the configuration data at runtime before bootstrapping the app. This is the approach I’m going to advocate for and show you in Part 2. With this approach, your code bundle (what you send to the browser) is the same for each environment. You can build once, deploy anywhere. You don’t need to add a complicated build tool if you don’t want, and it keeps all of your code and config strictly separated. The downside is that your app will need to make a network call at the very beginning, possibly slowing down initial bootstrapping of your application. This limitation can be overcome with heavy caching of config and smart UX design.
How to Use Environment Variables with Angular served from an Azure App Service
We can actually use some pretty cool magic to make this happen with Angular and an Azure App Service. To see how, check out How To: Using Environment Variables in the Browser with Angular and Azure App Services, Part 2.
How have you seen configuration and code separated on the front end? Share your story in the comments.