I got tired of writing the same CDK wiring, so I built simple-cdk
If you've written a few AWS CDK projects, you've probably noticed they all start the same way.
You spin up a stack. You add a NodejsFunction for each Lambda handler. You wire up a Table for each DynamoDB model, set billingMode, remember to turn on point-in-time recovery. You stand up an AppSync API, write a schema, then spend an afternoon writing CRUD resolvers that all look alike. You grant each Lambda the right IAM permissions on the right tables.
None of this is hard. It's just repetitive. Every new side project starts with 200 lines of the same wiring before you've written a single line of actual app code.
Across years of AWS projects, I kept running into the same wiring. Client work, side projects, internal tools: the same Lambda + DynamoDB + AppSync + Cognito shapes, written out by hand every time. I liked how simple Amplify made this. Scaffold a backend in minutes, wire it into a frontend, done. But I kept hitting the edges where its opinions turned into restrictions: schemas you can't customize without ejecting, infrastructure you can't shape past what Amplify exposes. What I wanted was that kind of simplicity without the walls. So I built simple-cdk, carrying forward the patterns that actually worked across those projects.
What it looks like
Create a project:
mkdir my-app && cd my-app
npx simple-cdk@latest init
The init command prompts you for an app name, region, default stage, and which adapters you want (lambda, dynamodb, appsync, cognito). Then it writes a config file and scaffolds the folders.
Your project ends up looking like this:
simple-cdk.config.ts
backend/
functions/
hello/handler.ts
models/
todo.model.ts
triggers/
post-confirmation/handler.ts
schema.graphql
A Lambda is "a folder with a handler.ts in it." A DynamoDB table is "a *.model.ts file." A Cognito trigger is "a folder named after the Cognito trigger." The CDK resources are created for you.
Deploy:
npx cdk bootstrap # one-time per region/account
npx simple-cdk deploy --stage dev
The adapter pattern
Under the hood, simple-cdk is built around adapters. Each adapter has three optional hooks:
-
discover(ctx)— find what you're responsible for (scan files, read config, etc.) and return a list ofResourceobjects -
register(ctx)— turn each resource into a CDK construct -
wire(ctx)— runs after every adapter'sregister, so you can look up other adapters' resources and connect them
The built-in adapters (@simple-cdk/lambda, @simple-cdk/dynamodb, @simple-cdk/appsync, @simple-cdk/cognito) all implement this same interface. Nothing special about them — they're just adapters that ship in the monorepo.
Which means writing your own is small:
// adapters/sqs.ts
import type { Adapter } from '@simple-cdk/core';
import { Duration, aws_sqs as sqs } from 'aws-cdk-lib';
export function sqsAdapter(opts: { queues: string[] }): Adapter {
return {
name: 'sqs',
discover: () =>
opts.queues.map((name) => ({
type: 'sqs-queue',
name,
source: 'config',
config: {},
})),
register: (ctx) => {
const stack = ctx.stack('queues');
for (const r of ctx.resources) {
const queue = new sqs.Queue(stack, r.name, {
queueName: `${ctx.config.app}-${ctx.config.stage}-${r.name}`,
visibilityTimeout: Duration.seconds(30),
});
(r.config as any).construct = queue;
}
},
};
}
Use it like any built-in:
adapters: [
lambdaAdapter(),
sqsAdapter({ queues: ['orders', 'emails'] }),
]
The escape hatch
This is the part that mattered most to me. I've watched teams pick a higher-level framework, hit the 20% of their use case that the framework didn't anticipate, and end up fighting it.
simple-cdk deliberately stays close to CDK. Three escape routes:
-
Override a built-in adapter. Pass your own object matching the same
name. The engine treats it as a replacement. - Write a small adapter for anything the built-ins don't cover (see above — it's not much code).
-
Drop down to raw CDK. The
wirehook gives youctx.stack(name)— you can new-up any CDK construct in there. The adapters don't own your stacks.
When NOT to use it
- You're building non-serverless workloads (ECS-heavy, EKS, EC2) — the built-in adapters won't help much, and raw CDK is fine for that.
- You already have a large, well-factored CDK codebase you're happy with — don't rewrite it.
- You want a fully managed full-stack framework that scaffolds frontend + backend together and hosts both. Amplify is a better fit for that.
Source: github.com/pujaaan/simple-cdk
npm: npx simple-cdk@latest init
Feedback, issues, and PRs welcome, especially on gaps where the escape hatches aren't enough. simple-cdk is running in production today, and the examples in the repo are real deployments.
Top comments (0)