> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding More Functionality

> Guide to extending a CDK for Terraform AWS project with reusable Lambda constructs, multiple stacks and environments, deterministic packaging, remote state backend, environment variable configuration, and deployment workflows

In this final section we recap the project and show practical ways to extend the application: adding new stacks for features or environments, and controlling runtime behavior via environment variables. The patterns below focus on CDK for Terraform (CDKTF) with AWS: building reusable Constructs (LambdaFunction, LambdaRestApi), packaging Lambda assets correctly, moving Terraform state to a remote backend (S3 + DynamoDB), and importing Terraform modules via CDKTF.

Key outcomes:

* Reusable Constructs: `LambdaFunction` and `LambdaRestApi`.
* Packaging strategy: prefer `TerraformAsset` over `execSync` for deterministic Lambda packaging.
* Remote backend: transition local state to S3/DynamoDB for team collaboration.
* CDKTF modules: import generated modules with `cdktf get`.

The deployed name-picker API returns a random family member to do chores — and the patterns here let Arthur add more features and environments safely.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/recap-slide-problem-manual-lambdafunction-packaging.jpg?fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=2ee5e90851af1826bc822f132557c1fc" alt="A slide titled &#x22;Recap&#x22; with a blue gradient sidebar and a vertical list of colorful numbered markers: 01 Problem, 02 Manual process, 03 LambdaFunction construct, and 04 Packaging with execSync vs TerraformAsset. The slide includes a small © KodeKloud notice at the bottom." width="1920" height="1080" data-path="images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/recap-slide-problem-manual-lambdafunction-packaging.jpg" />
</Frame>

## Adding a new stack (WeekPlannerStack)

To add a separate feature as its own deployable unit, create a new stack class that extends your shared `AwsBaseStack` (this base stack centralizes provider/backend configuration). The WeekPlanner example below demonstrates a minimal stack that reuses the base stack and exposes an output.

```typescript theme={null}
// stacks/WeekPlannerStack.ts
import { Construct } from 'constructs';
import { AwsBaseStack } from './AwsBaseStack';
import { TerraformOutput } from 'cdktf';

export class WeekPlannerStack extends AwsBaseStack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Pretend we deployed a resource and expose its URL as an output
    new TerraformOutput(this, 'weekPlannerUrl', {
      value: 'https://example.com',
    });
  }
}
```

Register the stack in your CDKTF app:

```typescript theme={null}
// main.ts
import { App } from 'cdktf';
import { NamePickerStack } from './stacks/NamePickerStack';
import { WeekPlannerStack } from './stacks/WeekPlannerStack';

const app = new App();
new NamePickerStack(app, 'cdktf-name-picker');
new WeekPlannerStack(app, 'cdktf-week-planner');
app.synth();
```

When multiple stacks exist the default CDKTF CLI needs an explicit target. Example CLI output when more than one stack is present:

```bash theme={null}
# Example console output when multiple stacks exist
Error: Usage Error: Found more than one stack, please specify a target stack.
Run cdktf deploy <stack> with one of these stacks: cdktf-name-picker, cdktf-week-planner
```

Deploy a specific stack by name:

```bash theme={null}
yarn deploy cdktf-week-planner
```

Or deploy multiple/all stacks using a quoted wildcard to prevent shell expansion:

```bash theme={null}
yarn deploy "*"
```

When you deploy, CDKTF will prompt to confirm which stacks to deploy and then print outputs from each stack (for example, the Week Planner URL).

## When to split functionality into separate stacks

A stack should map to a deployable unit of business functionality. Typical reasons to split into separate stacks:

| Reason                 | Description                                                   | Example                                                                         |
| ---------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| Feature isolation      | Deploy and maintain a feature independently from the main app | `WeekPlanner` is deployed separately from `NamePicker`                          |
| Environment separation | Keep `dev` and `prod` state and resources isolated            | `cdktf-name-picker` vs `cdktf-name-picker-prod`                                 |
| Team boundaries        | Allow teams to manage their own stacks and CI/CD pipelines    | A backend team manages a `PaymentsStack`, frontend team manages `FrontendStack` |
| Experimental work      | Try proofs-of-concept without affecting production            | Create a `feature-x` stack for testing                                          |

Below is the S3 console showing separate per-stack Terraform state files. Using a per-stack backend configuration results in distinct state objects per stack in your backend bucket.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/s3-console-cdktf-name-picker-objects.jpg?fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=ed7a473b6d8882ee82958086ec541846" alt="A screenshot of the Amazon S3 console showing the bucket &#x22;cdktf-name-picker-prereq-992382811848&#x22; with three objects listed (cdktf-name-picker, cdktf-name-picker-prod, cdktf-week-planner) and controls like Upload, Create folder, and Copy URL. The page also shows object sizes, last-modified timestamps, and the S3 navigation sidebar." width="1920" height="1080" data-path="images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/s3-console-cdktf-name-picker-objects.jpg" />
</Frame>

## Creating a prod stack (example)

A simple way to add a production environment is to instantiate the same stack class with a different ID. To avoid resource name collisions across stacks, use a helper that prefixes names with the stack identifier:

```typescript theme={null}
// utils/getConstructName.ts
import { TerraformStack } from 'cdktf';
import { Construct } from 'constructs';

export const getConstructName = (scope: Construct, id: string) =>
  `${TerraformStack.of(scope).node.id}-${id}`;
```

Instantiate dev and prod stacks in `main.ts`:

```typescript theme={null}
// main.ts
import { App } from 'cdktf';
import { NamePickerStack } from './stacks/NamePickerStack';

const app = new App();
new NamePickerStack(app, 'cdktf-name-picker');           // dev
new NamePickerStack(app, 'cdktf-name-picker-prod');      // prod
app.synth();
```

Deploying both stacks will create separate Terraform state files and outputs (for example, two API endpoints). Note: the StageName in the exercise example still shows `/dev` for both stacks — update stage naming if you want distinct stage paths for each environment.

## Visual: multiple stacks and components

This diagram shows three stacks (dev/prod for the name picker and a WeekPlanner). Each stack can contain Constructs such as `LambdaFunction` and `LambdaRestApi`.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/adding-more-stacks-namepicker-weekplanner.jpg?fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=a4a7fd53de2ac0b82b5a74109f33acc4" alt="A presentation slide titled &#x22;Adding More Stacks&#x22; showing three colorful boxes labeled &#x22;Name Picker Stack (dev)&#x22;, &#x22;Name Picker Stack (prod)&#x22;, and &#x22;Week Planner (dev)&#x22;, each containing components like &#x22;LambdaFunction Construct&#x22; and &#x22;LambdaRestApi Construct.&#x22; A footer reads &#x22;Multiple stacks illustrate different app components.&#x22;" width="1920" height="1080" data-path="images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/adding-more-stacks-namepicker-weekplanner.jpg" />
</Frame>

## Overriding runtime behavior with environment variables

Making Lambda runtime behavior configurable via environment variables is a practical way to change behavior without changing code. The name-picker Lambda supports a JSON array (`NAMES`) and a `SHUFFLE` flag. The handler below supports both modes:

* Roulette (random): return a random name on each invocation.
* Shuffle: maintain a shuffled in-memory list and return names sequentially; the in-memory state persists for the lifetime of the execution environment.

```javascript theme={null}
// function-name-picker/index.js
let shuffledNames = [];
let currentIndex = 0;

function shuffleArray(arr) {
  // Fisher-Yates shuffle
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}

exports.handler = async (event) => {
  console.log('Received event', JSON.stringify(event));

  // Parse environment variables with defaults
  const names = (() => {
    try {
      return JSON.parse(process.env.NAMES || '["Arthur","Martin","Douglas","Carolyn"]');
    } catch (e) {
      console.warn('Invalid NAMES env var, using default list', e);
      return ["Arthur","Martin","Douglas","Carolyn"];
    }
  })();

  const shuffle = (process.env.SHUFFLE || 'false') === 'true';

  if (!shuffle) {
    // Roulette/random mode: return a random name each invocation
    const randomName = names[Math.floor(Math.random() * names.length)];
    return {
      statusCode: 200,
      body: JSON.stringify(randomName),
    };
  } else {
    // Shuffle mode: maintain an in-memory shuffled list and return the next name
    if (!shuffledNames.length || currentIndex >= shuffledNames.length) {
      shuffledNames = shuffleArray([...names]);
      currentIndex = 0;
    }
    const selectedName = shuffledNames[currentIndex++];
    return {
      statusCode: 200,
      body: JSON.stringify(selectedName),
    };
  }
};
```

You can edit environment variables directly in the AWS Lambda console. The screenshot below shows the Lambda configuration page when no environment variables are set.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg?fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=30bba05546cb8021b1b030a28d7cd325" alt="Screenshot of the AWS Lambda console on a function's Configuration > Environment variables tab, showing &#x22;No environment variables&#x22; with an Edit button. The left navigation menu and a tutorial panel are also visible." data-og-width="1920" width="1920" data-og-height="1080" height="1080" data-path="images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg?w=280&fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=173e555e0799e5ffd4c6718f56d4ebae 280w, https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg?w=560&fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=1e1f321714367bb9ee01ec57fdd167f5 560w, https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg?w=840&fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=66d59b8f2cf4e9436fa1912b9b4e9c8e 840w, https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg?w=1100&fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=c44e400699a3e4231bfb9ada38869445 1100w, https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg?w=1650&fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=cdcb750281424f0beb915bb9504f987e 1650w, https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/aws-lambda-environment-variables-empty.jpg?w=2500&fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=c0a5d30b25396efa415b0eee36268ddc 2500w" />
</Frame>

Instead of manual edits, define default environment variables in your Lambda construct so they are deployed with the function. The project’s `LambdaFunction` construct forwards standard Lambda configuration (including `environment`) directly to the underlying AWS resource. Example:

```typescript theme={null}
// inside NamePickerStack.ts (example extract)
const functionNamePicker = new LambdaFunction(this, 'lambda-function', {
  functionName: getConstructName(this, 'api'),
  bundle: './function-name-picker',
  handler: 'index.handler',
  environment: {
    variables: {
      NAMES: '["Fred","Bob"]',
      SHUFFLE: 'false',
    },
  },
});

const lambdaRestApi = new LambdaRestApi(this, 'lambda-rest-api', {
  handler: functionNamePicker.lambdaFunction,
  stageName: 'dev',
});
```

Because the construct forwards arbitrary Lambda properties, you can add or change environment variables in your stack code and re-deploy — the deployment will overwrite manual console edits.

## Hints and type-safety (as const)

TypeScript's `as const` can help narrow literal types for compile-time checks. For example:

```typescript theme={null}
for (const type of ['roulette', 'shuffle'] as const) {
  // `type` is either 'roulette' or 'shuffle' as a string literal type
}
```

This pattern is optional but improves type-safety in code that branches on a small set of known values.

## How to deploy the full project (quick start)

Include a README with the following step-by-step commands for a fresh checkout. This short sequence is the recommended quick-start for collaborators:

<Callout icon="lightbulb" color="#1CB2FE">
  Run these commands after cloning to fetch providers/modules and deploy the backend and app stacks:

  * `yarn install` — install dependencies
  * `yarn cdktf get` — generate module bindings
  * `yarn deploy:prereq` — deploy the S3/DynamoDB remote backend
  * `yarn deploy "*"` — deploy all stacks (dev, prod, and feature stacks)
</Callout>

Notes:

* `yarn cdktf get` generates the `.gen` folder used for imported Terraform modules.
* `yarn deploy:prereq` deploys the prerequisite infrastructure (remote state bucket and locking table).

## package.json scripts

Here are the useful npm/yarn scripts included in the project and what they do:

| Script         | Command               | Purpose                                                                |
| -------------- | --------------------- | ---------------------------------------------------------------------- |
| get            | `yarn get`            | Runs `cdktf get` to generate module bindings                           |
| build          | `yarn build`          | Runs `tsc` to compile TypeScript                                       |
| synth          | `yarn synth`          | Runs `cdktf synth` to synthesize Terraform JSON                        |
| deploy         | `yarn deploy`         | Runs `cdktf deploy` for targeted stacks                                |
| deploy prereq  | `yarn deploy:prereq`  | Runs `cdktf deploy --app='yarn ts-node prereq.ts'` to create backend   |
| destroy        | `yarn destroy`        | Runs `cdktf destroy` to destroy stacks                                 |
| destroy prereq | `yarn destroy:prereq` | Runs `cdktf destroy --app='yarn ts-node prereq.ts'` to destroy backend |

Example `package.json` scripts snippet:

```json theme={null}
{
  "scripts": {
    "get": "cdktf get",
    "build": "tsc",
    "synth": "cdktf synth",
    "deploy": "cdktf deploy",
    "deploy:prereq": "cdktf deploy --app='yarn ts-node prereq.ts'",
    "destroy": "cdktf destroy",
    "destroy:prereq": "cdktf destroy --app='yarn ts-node prereq.ts'"
  }
}
```

## Cleaning up (destroying your infrastructure)

Destroying resources follows the reverse order of deployment. Important: destroy the application stacks first, then destroy the remote backend stack (the S3 bucket and DynamoDB table). If you destroy the backend first (remove the state bucket), Terraform will lose state and cannot reliably destroy the managed resources.

<Callout icon="warning" color="#FF6B6B">
  Warning: Always destroy application stacks before destroying the remote state backend. If the S3 bucket containing state is removed while resources still exist, Terraform cannot track or destroy those resources (you may encounter `BucketNotEmpty` or orphaned resources).
</Callout>

Steps to clean up:

1. Destroy application stacks:
   ```bash theme={null}
   yarn destroy "*"
   ```
2. Empty the backend S3 bucket if required (Terraform will fail with `BucketNotEmpty` if the bucket is not empty).
3. Destroy the prereq backend stack:
   ```bash theme={null}
   yarn destroy:prereq
   ```

The console shows an "Empty bucket" confirmation flow when you manually clear a bucket:

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/s3-empty-bucket-confirmation-screenshot.jpg?fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=369a2eaf45d147f888386a35adfc7da7" alt="A screenshot of the Amazon S3 console showing an &#x22;Empty bucket&#x22; confirmation page. It displays warnings, a textbox requiring you to type &#x22;permanently delete,&#x22; and an orange &#x22;Empty&#x22; button to confirm deletion." width="1920" height="1080" data-path="images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/s3-empty-bucket-confirmation-screenshot.jpg" />
</Frame>

After the backend is removed and everything is destroyed, the DynamoDB tables list should be empty:

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/q_P6afvbRoHuV6uC/images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/dynamodb-tables-empty-create-button.jpg?fit=max&auto=format&n=q_P6afvbRoHuV6uC&q=85&s=5453c05d4b5d2e815cb0924ba74f630b" alt="A screenshot of the AWS DynamoDB console showing the Tables page with no tables in this region and a prominent &#x22;Create table&#x22; button. The left sidebar lists DynamoDB navigation items like Dashboard, Explore items, Backups, and Settings." width="1920" height="1080" data-path="images/CDK-for-Terraform-with-TypeScript/AWS-With-CDKTF/Adding-More-Functionality/dynamodb-tables-empty-create-button.jpg" />
</Frame>

With these steps Arthur can fully tear down the application and avoid surprise cloud costs.

***

That concludes this lesson. The final summary covers everything learned across the course: problem definition, manual AWS deployment, reusable constructs, packaging strategies, remote backend setup, and importing modules via CDKTF — all combining to produce an automated, shareable, and maintainable infrastructure.

## Links and References

* CDK for Terraform (CDKTF) — [https://developer.hashicorp.com/terraform/cdktf](https://developer.hashicorp.com/terraform/cdktf)
* Terraform documentation — [https://www.terraform.io/docs](https://www.terraform.io/docs)
* AWS Lambda Developer Guide — [https://docs.aws.amazon.com/lambda/latest/dg/welcome.html](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html)
* S3 documentation — [https://docs.aws.amazon.com/s3/index.html](https://docs.aws.amazon.com/s3/index.html)
* DynamoDB documentation — [https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/cdk-for-terraform-with-typescript/module/4625ff69-dbd8-42ac-9542-d0e60a85e2ae/lesson/4c12891b-f4d1-42b9-a6d7-855716897e82" />

  <Card title="Practice Lab" icon="flask-conical" cta="Learn more" href="https://learn.kodekloud.com/user/courses/cdk-for-terraform-with-typescript/module/4625ff69-dbd8-42ac-9542-d0e60a85e2ae/lesson/b53f0026-82f8-4e86-b92d-abba0923d2b3" />
</CardGroup>
