<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: pujaaan</title>
    <description>The latest articles on Forem by pujaaan (@pujaaan).</description>
    <link>https://forem.com/pujaaan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3883266%2F670433d9-2c20-457a-8204-b15ad8c1801b.png</url>
      <title>Forem: pujaaan</title>
      <link>https://forem.com/pujaaan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pujaaan"/>
    <language>en</language>
    <item>
      <title>Stop rewriting the same CDK boilerplate - meet simple-cdk</title>
      <dc:creator>pujaaan</dc:creator>
      <pubDate>Thu, 16 Apr 2026 23:37:08 +0000</pubDate>
      <link>https://forem.com/pujaaan/i-got-tired-of-writing-the-same-cdk-wiring-so-i-built-simple-cdk-obg</link>
      <guid>https://forem.com/pujaaan/i-got-tired-of-writing-the-same-cdk-wiring-so-i-built-simple-cdk-obg</guid>
      <description>&lt;p&gt;Every AWS serverless backend I write starts the same way. A few Lambdas. A few DynamoDB tables. An AppSync API on top. Cognito for auth. Maybe RDS for the warehouse-y stuff. Then 200+ lines of CDK to wire it all together: same &lt;code&gt;Stack&lt;/code&gt; boilerplate, same &lt;code&gt;grantReadWriteData&lt;/code&gt;, same &lt;code&gt;addEnvironment('TABLE_NAME', table.tableName)&lt;/code&gt;, same dance, every time.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/pujaaan/simple-cdk" rel="noopener noreferrer"&gt;&lt;strong&gt;simple-cdk&lt;/strong&gt;&lt;/a&gt;. It's a thin convention layer on top of AWS CDK that turns a config file and a few folders into real CDK constructs. It's running production multi-tenant apps today. Here's what it is, what it isn't, and why the design choices matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What simple-cdk is
&lt;/h2&gt;

&lt;p&gt;A thin convention layer on top of AWS CDK. You describe your app in one &lt;code&gt;simple-cdk.config.ts&lt;/code&gt;, drop code into a few conventional folders, and built-in &lt;strong&gt;adapters&lt;/strong&gt; (&lt;code&gt;@simple-cdk/lambda&lt;/code&gt;, &lt;code&gt;@simple-cdk/dynamodb&lt;/code&gt;, &lt;code&gt;@simple-cdk/appsync&lt;/code&gt;, &lt;code&gt;@simple-cdk/cognito&lt;/code&gt;, &lt;code&gt;@simple-cdk/rds&lt;/code&gt;, &lt;code&gt;@simple-cdk/outputs&lt;/code&gt;) turn those folders into real CDK constructs.&lt;/p&gt;

&lt;p&gt;The engine does three things, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;discover&lt;/code&gt;&lt;/strong&gt;: scans your folders to find handlers, models, and triggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;register&lt;/code&gt;&lt;/strong&gt;: turns each one into a real CDK construct (a &lt;code&gt;lambda.Function&lt;/code&gt;, a &lt;code&gt;dynamodb.Table&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;wire&lt;/code&gt;&lt;/strong&gt;: a final pass where adapters can look up each other's constructs and connect them (this is when the AppSync adapter wires Lambda data sources, when Cognito triggers attach to the user pool, etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the whole engine. Nothing runs inside your AWS account. There's no agent watching your Lambdas, no SaaS dashboard you have to log into, no tracking of your deploys. The output is just a CloudFormation template, the same kind any other CDK app would produce. So your existing IAM auditing, your CloudFormation drift detection, and any deployment tooling you already use (CI, &lt;code&gt;cdk deploy&lt;/code&gt;, GitHub Actions) all keep working unchanged.&lt;/p&gt;

&lt;h2&gt;
  
  
  What simple-cdk is &lt;em&gt;not&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; a framework like Amplify Gen 2 or SST. There's no managed environment, no live-Lambda dev loop, no "log in to our dashboard," no proprietary resource types in your CloudFormation.&lt;/p&gt;

&lt;p&gt;It is also &lt;strong&gt;not opinionated about your folder structure&lt;/strong&gt; beyond defaults. Each adapter exposes an &lt;code&gt;opts.dir&lt;/code&gt; setting. Point it anywhere, swap the convention, or write your own &lt;code&gt;discover&lt;/code&gt; hook that reads from somewhere else entirely.&lt;/p&gt;

&lt;p&gt;And critically, adapters are &lt;strong&gt;not boilerplate generators&lt;/strong&gt;. They don't write code into your repo. They scan a folder you already have and turn its contents into CDK constructs at synth time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A complete app, in one file
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// simple-cdk.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@simple-cdk/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lambdaAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@simple-cdk/lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dynamoDbAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@simple-cdk/dynamodb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;appSyncAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@simple-cdk/appsync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultStage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;destroy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;retain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;logRetentionDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;lambdaAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;dynamoDbAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;appSyncAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;schemaFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schema.graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;generateCrud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;all&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a working backend. Lambdas auto-discovered from &lt;code&gt;backend/functions/&lt;/code&gt;, DynamoDB tables from &lt;code&gt;backend/models/&lt;/code&gt;, an AppSync GraphQL API with auto-generated CRUD resolvers wired to those tables. No stacks, no constructs, no IAM dance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx simple-cdk deploy &lt;span class="nt"&gt;--stage&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The adapter pattern (same every time)
&lt;/h2&gt;

&lt;p&gt;Every built-in follows the same shape: a &lt;code&gt;name&lt;/code&gt; plus up to three optional hooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Adapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;discover&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;   &lt;span class="c1"&gt;// find work (scan files, read config)&lt;/span&gt;
  &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// build CDK constructs&lt;/span&gt;
  &lt;span class="nx"&gt;wire&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// look up other adapters' resources&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing else to learn per service. The Cognito adapter and the RDS adapter and your own custom SQS adapter all use the same three hooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What adapters are for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Turning &lt;code&gt;backend/functions/foo/handler.ts&lt;/code&gt; into a &lt;code&gt;lambda.Function&lt;/code&gt; without you writing one&lt;/li&gt;
&lt;li&gt;Exposing the resulting CDK construct via lookup helpers (&lt;code&gt;getLambdaFunction(ctx, 'foo')&lt;/code&gt;) so you can grab and tweak it&lt;/li&gt;
&lt;li&gt;A single, predictable place to plug in cross-resource wiring (the &lt;code&gt;wire&lt;/code&gt; phase, which runs after every adapter's &lt;code&gt;register&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What adapters are &lt;em&gt;not&lt;/em&gt; for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating boilerplate code into your repo&lt;/li&gt;
&lt;li&gt;Hiding CDK behind a wrapper&lt;/li&gt;
&lt;li&gt;Forcing a specific folder layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one matters most. The folder convention is a default, not a mandate.&lt;/p&gt;

&lt;h2&gt;
  
  
  simple-cdk and raw CDK compose in the same project
&lt;/h2&gt;

&lt;p&gt;This is the part I most wanted to nail: simple-cdk doesn't replace CDK. It sits next to it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctx.stack(name)&lt;/code&gt; returns a real &lt;code&gt;cdk.Stack&lt;/code&gt;. Attach any construct from &lt;code&gt;aws-cdk-lib&lt;/code&gt; next to anything an adapter created.&lt;/li&gt;
&lt;li&gt;Built-in adapters expose lookup helpers (&lt;code&gt;getLambdaFunction&lt;/code&gt;, &lt;code&gt;getDynamoTable&lt;/code&gt;, &lt;code&gt;getUserPool&lt;/code&gt;, &lt;code&gt;getAppSyncApi&lt;/code&gt;, &lt;code&gt;getRdsInstance&lt;/code&gt;, etc.) that return underlying CDK constructs. Call any CDK method on them.&lt;/li&gt;
&lt;li&gt;A custom adapter is just a plain object. Inside &lt;code&gt;register&lt;/code&gt; or &lt;code&gt;wire&lt;/code&gt;, write whatever raw CDK code you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You can also embed simple-cdk inside an existing CDK app.&lt;/strong&gt; This is the feature I think is most underrated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// bin/app.ts (your existing CDK entry)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@simple-cdk/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lambdaAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@simple-cdk/lambda&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyExistingNetworkStack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../lib/my-existing-network-stack.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CDK_DEFAULT_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Your hand-written stacks, unchanged.&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MyExistingNetworkStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyExistingNetworkStack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// A stack you want simple-cdk's adapters to register into.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiStack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MyApiStack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultStage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;lambdaAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiStack&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;  &lt;span class="c1"&gt;// place auto-discovered Lambdas in your Stack&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;cdkApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two seams. &lt;code&gt;Engine.synth({ cdkApp })&lt;/code&gt; accepts a pre-built &lt;code&gt;cdk.App&lt;/code&gt;. Every adapter takes an optional &lt;code&gt;stack:&lt;/code&gt; to register its constructs into a &lt;code&gt;Stack&lt;/code&gt; your hand-written code already owns. This means an existing CDK project can adopt simple-cdk for one slice (say, auto-discover handlers in &lt;code&gt;backend/functions/&lt;/code&gt;) without rewriting anything else.&lt;/p&gt;

&lt;p&gt;The corollary: there's no real "outgrowing" simple-cdk. If a service or pattern doesn't fit a built-in adapter, you write a small custom one (&lt;code&gt;{ name, discover, register, wire }&lt;/code&gt;, no base classes) or drop raw CDK constructs into any stack the engine created. You don't have to leave, and even if you wanted to, the constructs are standard &lt;code&gt;aws-cdk-lib&lt;/code&gt; so the cost is just lifting them into a hand-written file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real production config looks like
&lt;/h2&gt;

&lt;p&gt;Built-ins and custom adapters sit in the same list. The engine doesn't care which is which. This is the adapter list from a production multi-tenant healthcare backend running on simple-cdk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nf"&gt;myDataAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;                    &lt;span class="c1"&gt;// custom: domain-shaped DynamoDB models with tenant-isolation&lt;/span&gt;
  &lt;span class="nf"&gt;cognitoAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;                    &lt;span class="c1"&gt;// built-in: user pool + 4 triggers + MFA + password policy&lt;/span&gt;
    &lt;span class="na"&gt;triggersDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;backend/auth/triggers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;mfa&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;optional&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;passwordPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requireSymbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;myAuthExtrasAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;              &lt;span class="c1"&gt;// custom: identity pool, SES, pre-token-generation grants&lt;/span&gt;
  &lt;span class="nf"&gt;myStorageAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;                 &lt;span class="c1"&gt;// custom: S3 buckets&lt;/span&gt;
  &lt;span class="nf"&gt;myFunctionsAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;               &lt;span class="c1"&gt;// custom: like @simple-cdk/lambda but with per-domain metadata&lt;/span&gt;
  &lt;span class="nf"&gt;myWarehouseAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;               &lt;span class="c1"&gt;// custom: RDS + VPC + DynamoDB stream consumer + EventBridge&lt;/span&gt;
  &lt;span class="nf"&gt;myApiAdapter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;                     &lt;span class="c1"&gt;// custom: uses buildApi() directly to drop resolvers into&lt;/span&gt;
                                      &lt;span class="c1"&gt;//         nested stacks (CFN 500-resource limit per stack)&lt;/span&gt;
  &lt;span class="nf"&gt;outputsAdapter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;                    &lt;span class="c1"&gt;// built-in: SSM parameter the frontend reads at boot&lt;/span&gt;
    &lt;span class="na"&gt;parameterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/my-app/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/aws-config`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stageConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userPoolId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getUserPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;userPoolId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userPoolClientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getUserPoolClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;userPoolClientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// ... plus identity pool id, AppSync URL, bucket names from custom adapters&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth highlighting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The custom API adapter uses &lt;code&gt;buildApi&lt;/code&gt; from &lt;code&gt;@simple-cdk/appsync&lt;/code&gt; directly&lt;/strong&gt; instead of the high-level &lt;code&gt;appSyncAdapter()&lt;/code&gt;. When the high-level adapter doesn't fit, you drop one level deeper and keep the CRUD pipeline. &lt;code&gt;buildApi&lt;/code&gt;, &lt;code&gt;attachCrudResolvers&lt;/code&gt;, and &lt;code&gt;attachManualResolvers&lt;/code&gt; are all exported for exactly this case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;outputsAdapter&lt;/code&gt; is the seam between custom and built-in.&lt;/strong&gt; It pulls the Cognito user pool from &lt;code&gt;@simple-cdk/cognito&lt;/code&gt;, the AppSync API from a custom adapter, and bucket names from another custom adapter, then bundles everything into one SSM parameter the frontend reads at boot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There is no "production mode" of simple-cdk.&lt;/strong&gt; Same engine, same adapter contract for the minimal example and the multi-adapter prod shape. You opt into complexity when you need it, only for the parts that need it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  simple-cdk vs raw CDK vs Amplify vs SST
&lt;/h2&gt;

&lt;p&gt;Honest comparisons. Pick the one that fits. They're not mutually exclusive forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs Raw CDK
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pick raw CDK&lt;/strong&gt; when your topology is unusual (deeply custom multi-stack graphs, multi-account fanout, non-serverless workloads), or when you want zero external dependencies beyond &lt;code&gt;aws-cdk-lib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick simple-cdk&lt;/strong&gt; when you find yourself writing the same Lambda + DynamoDB + AppSync wiring in every project, or when you want filesystem conventions for handlers and models without giving up the CDK escape hatch.&lt;/p&gt;

&lt;p&gt;You don't even have to pick one or the other &lt;em&gt;now&lt;/em&gt;. simple-cdk produces normal CDK constructs and accepts an existing &lt;code&gt;cdk.App&lt;/code&gt;. An existing CDK project can adopt simple-cdk for one slice without rewriting anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs AWS Amplify (Gen 2)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pick Amplify&lt;/strong&gt; when you want a fully managed full-stack framework that hosts frontend + backend together, you're fine living inside Amplify's deploy and runtime model, and you want first-class frontend codegen for Auth and Data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick simple-cdk&lt;/strong&gt; when you want Amplify's "drop a file in a folder, get a resource" feel but on plain CDK: no Amplify CLI, no Amplify Hosting, no Amplify-specific resource types in your CloudFormation. If Amplify Gen 2's restrictions don't fit (custom AppSync resolvers, cross-stack constructs, your own VPC), simple-cdk is closer to the metal.&lt;/p&gt;

&lt;h3&gt;
  
  
  vs SST
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pick SST&lt;/strong&gt; when you want their developer experience: &lt;code&gt;sst dev&lt;/code&gt; live Lambda, the SST console, sst.dev hosted features, Resource Linking. SST owns the deploy story end-to-end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pick simple-cdk&lt;/strong&gt; when you'd rather have less in the loop. simple-cdk just runs &lt;code&gt;cdk deploy&lt;/code&gt;. No daemon, no console, no SST-managed resources.&lt;/p&gt;

&lt;p&gt;Both Amplify and SST produce CDK/CloudFormation underneath, so migrating between any of these is mostly renaming, not rewriting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros, cons, when to use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pros.&lt;/strong&gt; Thin layer (engine is a few hundred lines and knows nothing about specific AWS services). Output is CloudFormation, no vendor lock-in beyond CDK itself. Mixable with raw CDK and embeddable inside an existing CDK app. Same three-hook adapter pattern across every service. Multi-stage out of the box. TypeScript-first, fully typed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons.&lt;/strong&gt; Built-ins target serverless: Lambda, DynamoDB, AppSync, Cognito, RDS, and an outputs SSM parameter ship today. Anything else, you write the adapter. No frontend or hosting. Small ecosystem (one maintainer). Convention-light: you still write a config file, this isn't "click a button and a backend appears."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use it:&lt;/strong&gt; TypeScript serverless backend on AWS (Lambda + DynamoDB + GraphQL or REST), team already uses CDK and is tired of repeating wiring, multi-stage deploys (&lt;code&gt;dev&lt;/code&gt; / &lt;code&gt;staging&lt;/code&gt; / &lt;code&gt;prod&lt;/code&gt; / sandboxes), you want filesystem conventions without giving up raw CDK as an escape hatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When &lt;em&gt;not&lt;/em&gt; to use it:&lt;/strong&gt; Non-serverless workloads (ECS/EKS/EC2-heavy), you want a fully managed full-stack platform (use Amplify or SST), or your team has zero appetite for a small dependency with one maintainer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-app
npx simple-cdk@latest init
npx cdk bootstrap
npx simple-cdk deploy &lt;span class="nt"&gt;--stage&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; walks you through app name, region, default stage, and which adapters to include, then writes a working &lt;code&gt;simple-cdk.config.ts&lt;/code&gt; and the &lt;code&gt;backend/&lt;/code&gt; folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/pujaaan/simple-cdk" rel="noopener noreferrer"&gt;github.com/pujaaan/simple-cdk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison page&lt;/strong&gt; (deeper side-by-side): &lt;a href="https://github.com/pujaaan/simple-cdk/blob/main/docs/Comparison.md" rel="noopener noreferrer"&gt;docs/Comparison.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture&lt;/strong&gt; (engine internals + lifecycle): &lt;a href="https://github.com/pujaaan/simple-cdk/blob/main/docs/Architecture.md" rel="noopener noreferrer"&gt;docs/Architecture.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding in an existing CDK app:&lt;/strong&gt; &lt;a href="https://github.com/pujaaan/simple-cdk/blob/main/docs/Adopting.md#embedding-in-an-existing-cdk-app" rel="noopener noreferrer"&gt;docs/Adopting.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Issues &amp;amp; discussions:&lt;/strong&gt; &lt;a href="https://github.com/pujaaan/simple-cdk/issues" rel="noopener noreferrer"&gt;github.com/pujaaan/simple-cdk/issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've used simple-cdk in anger, drop a note in the repo discussions. I'd love to know what's worked and what hasn't.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>showdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
