| description | Guidelines for writing, reviewing, and maintaining AWS CDK (TypeScript) code in the cdk package |
|---|---|
| applyTo | packages/cdk/**/*.ts |
This file is mastered in https://github.com/NHSDigital/eps-copilot-instructions and is automatically synced to all EPS repositories. To suggest changes, please open an issue or pull request in the eps-copilot-instructions repository.
This file provides instructions for generating, reviewing, and maintaining AWS CDK code in the packages/cdk folder. It covers best practices, code standards, architecture, and validation for infrastructure-as-code using AWS CDK in TypeScript.
- Use AWS CDK v2 constructs and idioms
- Prefer high-level CDK constructs over raw CloudFormation resources
- Organize code by logical infrastructure components (e.g., stacks, constructs, resources)
- Document public APIs and exported constructs
- Use environment variables and context for configuration, not hardcoded values
- Use CDK Aspects for cross-cutting concerns (e.g., security, tagging)
- Suppress warnings with
nagSuppressions.tsonly when justified and documented - Use
bin/for entrypoint apps,constructs/for reusable components, andstacks/for stack definitions - Prefer
propsinterfaces for construct configuration - For Step Functions definitions, prefer a chain-centric style where states are defined inline within
Chain.start(...).next(...)so the execution flow reads top-to-bottom in one place. Avoid mixing a chain with many separately declared stateconsts; instead embed calls to helper functions directly in the chain when needed. - For Step Functions chain formatting, place
.start,.next,.when, and.otherwiseon their own lines, and give helper calls such as.jsonata(...)the same line-break weight so nested flow blocks are visually aligned and easy to scan. - For construct props that group resources (for example lambda functions or state machines), prefer explicit named object shapes (e.g.
{status: TypescriptLambdaFunction}) over generic index signatures or broad maps so consumers are strongly typed to only the supported resources. - For construct props that consume grouped resources, prefer inline explicit object shapes in the props contract (for example
functions: { status: TypescriptLambdaFunction }) overPick<...>or generic map types.
interface ApisProps {
readonly functions: {
readonly status: TypescriptLambdaFunction
}
readonly stateMachines: {
readonly getMyPrescriptions: ExpressStateMachine
}
}Bad Example - Hidden Contract via Pick
interface ApisProps {
readonly functions: Pick<FunctionResources, "status" | "capabilityStatement">
}interface ApisProps {
functions: {[key: string]: TypescriptLambdaFunction}
stateMachines: {[key: string]: ExpressStateMachine}
}- Classes: PascalCase (e.g.,
LambdaFunction) - Files: PascalCase for classes, kebab-case for utility files
- Variables: camelCase
- Stacks: Suffix with
Stack(e.g.,CptsApiAppStack) - Entry points: Suffix with
App(e.g.,CptsApiApp.ts) - CDK app entry points must follow
<app acronym><Api|Ui>[Sandbox]Appnaming (e.g.,PsuApiApp,PsuApiSandboxApp)
bin/: CDK app entry pointsconstructs/: Custom CDK constructsstacks/: Stack definitionsresources/: Resource configuration and constantslib/: Shared utilities and code
export class LambdaFunction extends Construct {
constructor(scope: Construct, id: string, props: LambdaFunctionProps) {
super(scope, id);
// ...implementation...
}
}const lambda = new cdk.CfnResource(this, 'Lambda', {
type: 'AWS::Lambda::Function',
// ...properties...
});export class CptsApiAppStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// ...add constructs...
}
}- Use least privilege IAM policies for all resources
- Avoid wildcard permissions in IAM statements
- Store secrets in AWS Secrets Manager, not in code or environment variables
- Enable encryption for all data storage resources
- Use provisioned concurrency for Lambda functions when needed
- Prefer VPC endpoints for private connectivity
- Minimize resource creation in test environments
- Write unit tests for CDK stacks and constructs using synthesis-based assertions.
- Prefer in-process tests that instantiate CDK
AppandStackobjects directly and assert on synthesized templates. - Keep assertions light-touch and stable, such as resource counts and a small number of important properties.
- Avoid mocking AWS resources or writing tests that attempt to exercise live AWS behaviour.
- CDK constructs suitable for reuse should be placed in
eps-cdk-utilsrepo. - Do not test AWS implementation details owned by the CDK library. Test the resources and properties your code is responsible for declaring.
- Smoke tests for
bin/files: execute the entrypoint and assert that synthesis completes without throwing. - In-process synth tests for stacks and constructs: instantiate the stack directly and assert resource counts or key CloudFormation properties with
Template.fromStack(...).
- Build:
make cdk-synth - Lint:
npm run lint --workspace packages/cdk - Test:
npm test --workspace packages/cdk
- Update dependencies regularly
- Remove deprecated constructs and suppressions
- Document changes in
nagSuppressions.tswith reasons