You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: chapters/ch06.asciidoc
+86Lines changed: 86 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -30,6 +30,92 @@ Another benefit of going down this road is that, given we have all environment c
30
30
31
31
When it comes to sharing the secrets, given we're purposely excluding them from source version control, we can take many approaches, such as using environment variables, storing them in JSON files kept in an Amazon S3 bucket, or using an encrypted repository dedicated to our application secrets.
32
32
33
+
One concrete and effective way of accomplishing this in real-world environments is using several "dot env" files, each with a clearly defined purpose. In order of precedence:
34
+
35
+
- `.env.defaults.json` can be used to define default values that aren't necessarily overwritten across environments, such as the application listening port, the `NODE_ENV` variable, and configurable options you don't want to hard-code into your application code. These default settings should be safe to check into source control
36
+
- `.env.production.json`, `.env.staging.json`, and others can be used for environment-specific settings, such as the various production connection strings for databases, cookie encoding secrets, API keys, and so on
37
+
- `.env.json` could be your local, machine-specific settings, useful for secrets or configuration changes that shouldn't be shared with other team members
38
+
39
+
Furthermore, you could also accept simple modifications to environment settings through environment variables, such as when executing `PORT=3000 node app`, which is often convenient during development.
40
+
41
+
There's an npm package called `nconf` which we can use to handle reading and merging all of these sources of application settings with ease.
42
+
43
+
The following piece of code shows how you could configure `nconf` to do what we've just described.
The module also exposes an interface through which we can consume these application settings by making a function call such as `env('PORT')`.
68
+
69
+
Assuming we have an `.env.defaults.json` that looks like the following, we could pass in the `NODE_ENV` flag when starting our staging, test, or production application and get the proper environment settings back, helping us simplify the process of loading up an environment.
70
+
71
+
```
72
+
{
73
+
"NODE_ENV": "development"
74
+
}
75
+
```
76
+
77
+
When it comes to the browser, we could use the exact same files and environment variables, but include a dedicated browser-specific object field, like so:
78
+
79
+
```
80
+
{
81
+
"NODE_ENV": "development",
82
+
"BROWSER_ENV": {
83
+
"MIXPANEL_API_KEY": "some-api-key",
84
+
"GOOGLE_MAPS_API_KEY": "another-api-key"
85
+
}
86
+
}
87
+
```
88
+
89
+
Then, we could write a tiny script like the following to print all of those settings.
90
+
91
+
```
92
+
// print-browser-env
93
+
import env from './lib/env'
94
+
console.log(env('BROWSER_ENV'))
95
+
```
96
+
97
+
Naturally, we don't want to mix server-side settings with browser settings, because browser settings are usually accessible to anyone with a user agent, the ability to visit our website, and basic programming skills, meaning we would do well not to bundle highly sensitive secrets with our client-side applications. To resolve the issue, we can have a build step that prints the settings for the appropriate environment to an `.env.browser.json` file, and then only use that file on the client-side.
98
+
99
+
```
100
+
node print-browser-env
101
+
```
102
+
103
+
Furthermore, we should replicate the `env` file from the server-side in the client-side, so that application settings are consumed in much of the same way in both sides of the wire.
104
+
105
+
```
106
+
// client/env
107
+
import env from './env.browser.json'
108
+
109
+
export default function accessor(key) {
110
+
if (typeof key === 'string') {
111
+
return env[key]
112
+
}
113
+
return env
114
+
}
115
+
```
116
+
117
+
There are many other ways of storing our application settings, each with their pros and cons. The approach we just discussed, though, is relatively easy to implement and solid enough to get started. As an upgrade, you might want to look into using AWS Secrets Manager. That way, you'd have a single secret to take care of in team members' environments, instead of every single secret. A secret service also takes care of encryption, secure storage, secret rotation (useful in the case of a data breach), among other advanced features.
118
+
33
119
==== 6.2 Explicit Dependency Management
34
120
35
121
It's not practical to include dependencies in our repositories, given these are often in the hundreds of megabytes and often include environment-dependant and operating system dependant files. During development, we want to make sure we get non-breaking upgrades to our dependencies, which can help us resolve bugs and tighten our grip around security vulnerabilities. For deployments, we want reproducible builds, where installing our dependencies yields the same results every time. The solution is often to include a dependency manifest, indicating what exact versions of the libraries in our dependency tree we want to be installing. This can be accomplished with npm (starting with version 5) and its `package-lock.json` manifest, as well as through the Yarn package manager and its `yarn.lock` manifest, both of which we should be publishing to our versioned repository.
0 commit comments