Configuration
The Problem
There are many places you could store your configuration with your full stack cloud native web apps on AWS including: static code, environment variables, Amazon S3, AWS Systems Manager Parameter Store, AWS Secrets Manager, AWS AppConfig, and more! Where you do you store your configuration? Additionally, when working with Next.js, how do we ensure server configuration doesn't get included in client bundles?
Balanced Approach
We believe using static code for most configuration and AWS SSM Parameter Store Secure Strings for most secrets is a great balance between simplicity and features. Static code configuration is simple to create, easy to dynamically utilize based on environment, version controlled, and updated on each deployment. AWS SSM Parameter Store Secure Strings is simple and inexpensive. If you have enterprise grade configuration management needs or dynamic configuration, please consider AWS AppConfig and AWS Secrets Manager.
Shared Configuration
In every source (src) directory of Green Boost templates, there is a config folder. This folder contains the static code configuration for the application. For example, look at core/src/config/shared-config.ts from the BasicUI
template below:
export class SharedConfig {
static appId = "myapp";
constructor(stageName?: string) {
this.stageName = stageName || StageName.Local;
}
get enumStageName(): StageName {
return Object.values(StageName).includes(this.stageName as StageName)
? (this.stageName as StageName)
: StageName.Local;
}
get isLocal() {
return this.enumStageName === StageName.Local;
}
get isProd() {
return this.enumStageName === StageName.Prod;
}
stageName: string;
}
This configuration class gives us key information about our application so that we can change the behavior of based on it's stage or environment. For example, in development we can only deploy a single writer instance as the load on our DB in development is low but in production we can add additional reader instances. Using code to store our configuration in a class means we can export the configuration class to other workspaces (infra, ui, etc.) and then inherit from this class to easily use the configuration in different environments.
Keep Server Configuration out of Client Components
The above shared configuration pattern can be used throughout our app. But what happens when begin to add configuration that we want to make sure isn't exposed on the client? For example, checkout core/src/config/server-config.ts from the CRUDPostgres
template below:
export class ServerConfig extends SharedConfig {
static get dbAdminUsername() {
return `${SharedConfig.appId}_admin`;
}
static get dbIamUsername() {
return `${SharedConfig.appId}_iam_user`;
}
static get dbName() {
return `${SharedConfig.appId}_db`;
}
static envVarNames = {
STAGE_NAME: "STAGE_NAME",
NEXT_PUBLIC_STAGE_NAME: "NEXT_PUBLIC_STAGE_NAME",
};
constructor(stageName?: string) {
super(stageName || StageName.Local);
}
}
We want to make sure the database configuration values are not accidentally included on the client side. To do this, we can export our server configuration from a sub-module export through core/package.json#exports called "server". Then in our ESLint configuration at /packages/eslint-config-next we can create a rule prevents importing from @myapp/core/server
where the name of the package in core/ is @myapp/core
. Then we can create a ui/src/core-server.ts like below:
import "server-only"; // prevents server code from being bundled into client code
// eslint-disable-next-line no-restricted-imports
export * from "@myapp1/core/server";
Here, we temporarily disable the eslint restriction to gain access to the configuration and re-export the value but we ensure to import the server-only package. This package ensures that only React Server Components can safely import this file. If a client component attempts to import the code, the build will fail. Additionally, if you try import from @myapp1/core/server
within your ui folder, you'll get the (good) error: import is restricted from being used. Importing from @myapp/core/server directly could result in server code leaking into the client. Please import from '@/core-server'.
🛡️.
Secret Configuration
When you need to use secrets within your application, do not statically include secrets in your code or through static environment variables. At runtime, fetch the secrets and use them or dynamically inject them into your environment if needed. Use @aws-sdk/client-ssm
with the GetParameterCommand (opens in a new tab) to fetch secure strings. Don't forget to set WithDecryption: true
.