DEV Community

Dinesh_gowtham
Dinesh_gowtham

Posted on

How to Turn Natural Language Prompts into CDK TypeScript Stacks with Claude – A Step‑By‑Step Guide

Ever wished you could describe your cloud resources in plain English and watch CDK spin them up? Claude’s function‑calling can turn a single prompt into fully‑typed TypeScript infrastructure. Let’s see why this feels like having a junior engineer who never sleeps.

Prompt Crafting for Infra

Why spend time on the prompt?

Think of a prompt as a recipe you give to a chef. If the recipe is vague, the dish will be bland or even inedible. The same is true for AI‑generated infrastructure: a well‑structured prompt tells Claude exactly what you need, how it should behave, and what constraints to respect. When you include details such as “enable versioning”, “add a 30‑day lifecycle rule”, and “use AES‑256 encryption”, Claude can emit CDK code that already respects best practices.

How to write a good prompt

  1. State the resource type – “Create an S3 bucket”.
  2. List required features – versioning, lifecycle, encryption.
  3. Mention constraints – bucket name must be globally unique, keep the stack under 500 resources.
  4. Ask for a specific output – “Give me a single TypeScript file that exports a class called BucketStack”.

Below is a minimal prompt that follows those steps:

Please generate a TypeScript CDK construct that creates an S3 bucket with:
- versioning enabled,
- a lifecycle rule that deletes objects after 30 days,
- server‑side encryption using AES‑256,
- a bucket name that includes the word "demo".
Return the code as a complete file named lib/bucket-stack.ts and make sure it compiles with `aws-cdk-lib@2.x`.
Enter fullscreen mode Exit fullscreen mode

Key takeaway: A prompt is a contract. The clearer the contract, the fewer revisions you’ll need later.

Claude‑Generated TypeScript Code

Why let Claude write the code?

You could hand‑write the same construct, but Claude can produce a type‑correct file in seconds. This speeds up prototyping, especially when you’re still exploring which AWS services you need. The generated code still runs through the TypeScript compiler, so any typo or missing import will be caught immediately.

How to request the code

Claude’s function‑calling feature lets you describe a “function” the model should fill in. In our case, the function is generateCdkFile with parameters filename and content. The model returns JSON that we can write to disk.

// utils/claude.ts
import { Anthropic } from '@anthropic-ai/sdk'; // SDK for Claude
import fs from 'fs/promises';

// Define the shape of the function we ask Claude to call
type GenerateCdkFileParams = {
  filename: string; // where the code should be saved, e.g. "lib/bucket-stack.ts"
  content: string;  // the full TypeScript source code
};

// Helper that calls Claude’s function‑calling endpoint
export async function askClaudeForCdk(
  description: string
): Promise<GenerateCdkFileParams> {
  const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

  // The “messages” array is the chat history; we only need a system prompt and the user description
  const response = await client.messages.create({
    model: 'claude-3-5-sonnet-20241022', // latest model as of 2026
    max_tokens: 4000,
    temperature: 0, // deterministic output for code
    messages: [
      {
        role: 'system',
        content:
          'You are an expert AWS CDK developer. When asked, output a single TypeScript file that compiles with aws-cdk-lib@2. Use the function "generateCdkFile" to return the filename and content.',
      },
      { role: 'user', content: description },
    ],
    // Declare the function we expect back
    tools: [
      {
        type: 'function',
        function: {
          name: 'generateCdkFile',
          description: 'Return a filename and TypeScript source for a CDK construct',
          parameters: {
            type: 'object',
            properties: {
              filename: { type: 'string' },
              content: { type: 'string' },
            },
            required: ['filename', 'content'],
          },
        },
      },
    ],
  });

  // Claude may return the function call in `tool_calls[0].function`
  const toolCall = response?.tool_calls?.[0]?.function;
  if (!toolCall) throw new Error('Claude did not return a generateCdkFile call');

  const result: GenerateCdkFileParams = JSON.parse(toolCall.arguments);
  // Write the file to disk for the next step
  await fs.writeFile(result.filename, result.content);
  return result;
}
Enter fullscreen mode Exit fullscreen mode

In plain English: We tell Claude what we want (a TypeScript file) and Claude hands us the exact text, which we then save.

Type‑Safe Validation with satisfies

Why add an extra type check?

Even though Claude aims to produce correct TypeScript, it can still make subtle mistakes—like using a property that doesn’t exist on a construct. The satisfies operator (available since TypeScript 4.9) lets us verify that the generated object conforms to an interface without changing its inferred type. This gives us a safety net before we ever run cdk synth.

How to validate the generated stack

First, define a minimal interface that captures the shape we expect:

// src/types.ts
import { StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

// Interface describing the constructor signature of our generated stack
export interface BucketStackCtor {
  new (scope: Construct, id: string, props?: StackProps): unknown;
}
Enter fullscreen mode Exit fullscreen mode

Now import the generated class (it will be in lib/bucket-stack.ts) and assert it satisfies the interface:

// src/validate.ts
import { BucketStackCtor } from './types';
import * as path from 'path';

// Dynamically import the generated file; Node's ESM loader works with .ts if ts-node is present
import('ts-node/register').then(() => {
  const generated = require(path.resolve('lib/bucket-stack.ts')) as {
    BucketStack: BucketStackCtor;
  };

  // The `satisfies` check runs at compile time; we also guard at runtime
  const isValid = (generated.BucketStack as any) as BucketStackCtor;
  // If TypeScript compilation succeeded, the shape matches; otherwise we get a type error.
  console.log('✅ Generated stack matches expected constructor signature.');
});
Enter fullscreen mode Exit fullscreen mode

Tip: Keep the validation file separate from your production code. It acts like a quick “unit test” that you run after each AI generation.

Integrating into a CDK App

Why wrap the stack in an app?

A CDK App (short for application) is the entry point that tells the CDK framework which stacks to synthesize. By adding the AI‑generated stack to an existing app, you can treat it like any hand‑written stack—run cdk diff, cdk synth, or cdk deploy.

How to add the stack

Create a standard CDK entry file (bin/main.ts). This file imports the generated stack class and adds it to the app.

// bin/main.ts
import * as cdk from 'aws-cdk-lib';
import { BucketStack } from '../lib/bucket-stack';

// CDK bootstrap is required once per account/region. Without it, deployment fails.
const app = new cdk.App();

// The stack id must be unique within the app.
new BucketStack(app, 'DemoBucketStack', {
  // You can pass standard StackProps here (env, tags, etc.)
  env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});

app.synth(); // Generates CloudFormation templates in the cdk.out directory
Enter fullscreen mode Exit fullscreen mode

In plain English: The App is like a container that holds one or more stacks. Adding the AI‑generated BucketStack is no different than adding a manually written one.

Known CDK gotchas (quick reminder)

Gotcha What happens How to avoid
Bootstrap required Deploy fails with “Bootstrap stack not found”. Run cdk bootstrap aws://ACCOUNT/REGION once per region.
Large stacks (>500 resources) CloudFormation rejects the template. Split logical groups into separate stacks.
Aspects fire after synth Trying to modify a resource inside an Aspect won’t work. Use Aspects only for read‑only inspection or add constructs before app.synth().
Token resolution Some values (like Fn::GetAtt) appear as unresolved tokens and can’t be used in string interpolation. Keep token‑dependent logic inside CDK constructs, not in plain JS strings.

Deploying via CDK Pipelines

Why use a pipeline?

Running cdk deploy manually is fine for experiments, but a CDK Pipeline (part of the aws-cdk-lib/pipelines module) gives you repeatable, version‑controlled deployments. The pipeline can pull the latest AI‑generated code from your repository, synthesize the stack, and deploy it to a target account—all without a human pressing a button.

How to set up a simple pipeline

First, add the pipeline module to your package.json:

npm install aws-cdk-lib@2 aws-cdk-lib/pipelines
Enter fullscreen mode Exit fullscreen mode

Now create a pipeline stack (lib/pipeline-stack.ts):

// lib/pipeline-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as pipelines from 'aws-cdk-lib/pipelines';
import { Construct } from 'constructs';
import { BucketStack } from './bucket-stack';

export class PipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Source action – assumes the code lives in a GitHub repo
    const source = pipelines.CodePipelineSource.gitHub('my-org/my-repo', 'main', {
      authentication: cdk.SecretValue.secretsManager('github-token'), // store token in Secrets Manager
    });

    // Synthesize step – runs `npm ci && npx cdk synth`
    const synth = new pipelines.ShellStep('Synth', {
      input: source,
      commands: [
        'npm ci',
        // Optional: run validation script that checks `satisfies`
        'node src/validate.js',
        'npx cdk synth',
      ],
    });

    // Build the pipeline
    new pipelines.CodePipeline(this, 'Pipeline', {
      pipelineName: 'DemoBucketPipeline',
      synth,
      // Add a stage that deploys the bucket stack to the "prod" environment
      // (you could add more stages for test, staging, etc.)
      deployStage: new pipelines.Stage(this, 'Prod', {
        stacks: [new BucketStack(this, 'ProdBucketStack')],
      }),
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, reference the pipeline stack in your bin/main.ts (you can keep the original bucket stack for local testing):

// bin/main.ts (updated)
import * as cdk from 'aws-cdk-lib';
import { BucketStack } from '../lib/bucket-stack';
import { PipelineStack } from '../lib/pipeline-stack';

const app = new cdk.App();

// Local preview stack
new BucketStack(app, 'DemoBucketStack');

// CI/CD pipeline stack (will be deployed to a dedicated pipeline account)
new PipelineStack(app, 'DemoPipelineStack', {
  env: { account: process.env.CDK_PIPELINE_ACCOUNT, region: process.env.CDK_PIPELINE_REGION },
});

app.synth();
Enter fullscreen mode Exit fullscreen mode

Tip: Remember the CDK token limit of 500 resources. If the pipeline tries to synthesize a stack that exceeds this limit, the pipeline will fail early, saving you time and money.

Dealing with Claude’s token limit

Claude truncates responses that exceed its token budget. When you ask for a very large stack, the model may return only the first part of the file. To avoid this:

  1. Ask for chunks – “Give me the first 200 lines, then the next 200 lines.”
  2. Use the streaming API – it sends tokens as they are generated, letting you concatenate the pieces on the client side.
  3. Keep prompts focused – generate one logical unit (e.g., an S3 bucket) at a time, then compose them in your CDK app.

In plain English: Think of Claude like a short‑hand writer; if the page gets too crowded, it stops. Break the job into smaller pages, or let Claude write continuously and stitch the pages together.

The Takeaway

You now have a repeatable workflow that turns a natural‑language description into a type‑safe CDK stack, validates it, and deploys it through an automated pipeline.

  • Prompt matters more than code. A clear recipe yields correct infrastructure.
  • Claude’s function‑calling returns raw TypeScript that you can write straight to disk.
  • satisfies gives compile‑time confidence that the AI‑generated class matches the shape you expect.
  • CDK apps treat generated stacks like any other; just remember to bootstrap the target account/region.
  • Pipelines make deployment reproducible and catch errors (including token‑limit truncation) before they reach production.
  • Chunked or streaming responses keep Claude from cutting off code when you need larger constructs.

Give it a try: describe a DynamoDB table, an SNS topic, or even a full VPC in plain English, and watch Claude hand you ready‑to‑deploy TypeScript. Happy building!


Transparency notice

This article was written with the help of an AI system — Groq (GPT OSS 120B).

Published: 2026-09-04 · Primary focus: CDK

All code blocks are intended to be correct and runnable, but please verify them
against the official docs for the tools mentioned before using in production.

Find an error? Drop a comment — corrections are always welcome.

Top comments (0)