pfried
pfried

Reputation: 5079

How to pass configuration to a Webapp used in a AWS-CDK stack

Lets say you have a AWS CDK Stack creating some resources

  1. Cognito User Pool
  2. AppSync Endpoint

which get utilized by a Webapp you want to deploy to S3.

How would you programmatically pass configuration like the endpoint url of your appsync endpoint to the application you want to deploy?

AWS Amplify creates a aws-exports.js file which the app uses. The file is created by some amplify commands and put into the application directory to be accessed from the app.

Is there any tooling or recommendation on how to solve this without using amplify? Would be great if someone had some examples or ideas on it.

I am using a standard React app created with the generator.

Upvotes: 2

Views: 1154

Answers (2)

pfried
pfried

Reputation: 5079

There is actually a really excellent articel from Maciek Wątroba at https://aws.plainenglish.io/cdk-pipelines-beyond-basics-37b731b7a182 explaining how to inject the dependencies with aws cdk

Upvotes: 0

Mathieu Gilbert
Mathieu Gilbert

Reputation: 86

I know this was asked a few months ago, but I arrived here while trying to figure it out on my own.

The challenge is the config values aren't available until after the CDK deploy, so you can't just add a script to write the stack outputs. My solution was to add my own deploy script, which first deploys the backend stacks, then builds the config, then deploys the web stack.

Invoke script with npm run deploy:

cdk/package.json

"deploy": "node deploy"

cdk/deploy.js

const execSync = require('child_process').execSync

const exec = command => {
  execSync(command, {
    stdio: [0, 1, 2]
  })
}

console.log('- Deploying backend.')
exec(`cdk deploy --all --require-approval never -c deploy=backend`)

console.log('- Generating frontend config.')
exec(`npm run build-config --prefix ../web`) // "node scripts/buildConfig.js"

console.log('- Building frontend.')
exec(`npm run build --prefix ../web`) // "react-scripts build"

console.log('- Deploying frontend.')
exec(`cdk deploy --all --require-approval never -c deploy=frontend`)

Conditional stack deploys:

cdk/cdk.ts

const deploy = app.node.tryGetContext('deploy')

if (deploy === 'backend') {
  new AuthStack(...)
  new ApiStack(...)
}

if (deploy === 'frontend') {
  new WebStack(...)
}

Output the values you need for the config:

cdk/lib/auth-stack.ts

...
const userPool = new cognito.UserPool(...)

this.exportValue(userPool.userPoolId, {
  name: 'UserPoolId',
})
...

Read the outputs and build the config:

web/scripts/buildConfig.js

const fs = require('fs')
const path = require('path')
const { CloudFormation } = require('aws-sdk')

const region = 'us-west-2'
const cloudformation = new CloudFormation({ region })

const outputs = {}

// get stack info
const apiStack = await cloudformation.describeStacks({ StackName: 'ApiStack' }).promise()
const authStack = await cloudformation.describeStacks({ StackName: 'AuthStack' }).promise()

// build the outputs into a simple object
apiStack.Stacks[0].Outputs.forEach(({ ExportName, OutputValue }) => { outputs[ExportName] = OutputValue })
authStack.Stacks[0].Outputs.forEach(({ ExportName, OutputValue }) => { outputs[ExportName] = OutputValue })

// read existing config (this is our version of aws-exports)
const configFilePath = path.join(__dirname, '..', 'src', 'config.json')
const config = JSON.parse(fs.readFileSync(configFilePath))

// build config:
config.Auth = {
  region,
  userPoolId: outputs.UserPoolId,
  userPoolWebClientId: outputs.WebUserPoolClientId,
}
config.API = {
  aws_appsync_graphqlEndpoint: outputs.AppSyncURL,
  aws_appsync_region: region,
  aws_appsync_authenticationType: 'AMAZON_COGNITO_USER_POOLS',
  aws_project_region: region,
  aws_cognito_region: region,
  aws_user_pools_id: outputs.UserPoolId,
  aws_user_pools_web_client_id: outputs.WebUserPoolClientId,
}

// update the file
fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2))

Within React:

web/src/App.tsx

import * as config from './config.json'

Amplify.configure({
  Auth: config.Auth,
  API: config.API,
})

Upvotes: 7

Related Questions