Build on AWS without being an AWS expert.
simple-cdk is a thin layer on top of AWS CDK. You describe your app once in a single config file, drop your code into a few conventional folders, and the built-in adapters turn it into Lambda functions, DynamoDB tables, an AppSync GraphQL API, a Cognito user pool, an RDS database, and a bundled outputs parameter for frontends. Every adapter is optional, every adapter is replaceable, and you can drop down to raw CDK any time.
simple-cdk is for teams that want to ship AWS serverless backends — Lambda, DynamoDB, AppSync (GraphQL), Cognito — without writing a mountain of CDK boilerplate. It's convention-over-configuration for AWS CDK, borrowing the scaffold-and-go simplicity of tools like AWS Amplify while staying on plain CDK so you keep an escape hatch into raw constructs.
Reach for simple-cdk when you want to:
- Deploy AWS Lambda + DynamoDB + GraphQL without hand-wiring CDK stacks for every resource
- Use AWS CDK but stop repeating the same resource-wiring patterns across projects
- Scaffold a TypeScript serverless backend on AWS in one command (
npx simple-cdk init) - Auto-generate CRUD resolvers for DynamoDB-backed AppSync APIs
- Keep Amplify-style scaffolding ergonomics on projects where Amplify's restrictions won't fit
Prefer plain CDK or Amplify when: you have non-serverless workloads (ECS/EKS/EC2-heavy), deeply custom multi-stack topologies, an existing CDK codebase you're happy with, or you want a fully managed full-stack framework that hosts frontend + backend together.
Run this in any folder you want your project to live in: a brand new empty directory, or the root of an existing repo.
mkdir my-app && cd my-app # or: cd into an existing project root
npx simple-cdk@latest init # prompts you, then installs + scaffoldsinit walks you through:
- App name, AWS region, default stage
- Which built-in adapters to include (
lambda,dynamodb,appsync,cognito)
Then it installs the right packages, writes a working simple-cdk.config.ts, and creates the backend/ folders for the adapters you picked. When it's done:
npx cdk bootstrap # one-time per region/account
npx simple-cdk deploy --stage dev # push to AWSYou'll need Node 22+ and AWS credentials (aws configure or AWS_PROFILE) on the machine you run this from.
See examples/01-minimal and examples/02-with-models for fully wired projects.
Skip init if you'd rather wire things up by hand or pin specific versions.
npm install aws-cdk-lib constructs aws-cdk
npm install @simple-cdk/core simple-cdk
# adapters: install only the ones you need
npm install @simple-cdk/lambda @simple-cdk/dynamodb @simple-cdk/appsync @simple-cdk/cognito
# optional extras: RDS and a bundled-outputs SSM parameter for frontends
npm install @simple-cdk/rds @simple-cdk/outputsThen create simple-cdk.config.ts at your project root:
import {defineConfig} from '@simple-cdk/core'
import {lambdaAdapter} from '@simple-cdk/lambda'
import {dynamoDbAdapter} from '@simple-cdk/dynamodb'
import {appSyncAdapter} from '@simple-cdk/appsync'
export default defineConfig({
app: 'my-app',
defaultStage: 'dev',
stages: {
dev: {region: 'us-east-1', removalPolicy: 'destroy'},
prod: {region: 'us-east-1', removalPolicy: 'retain', logRetentionDays: 365},
},
adapters: [
lambdaAdapter(),
dynamoDbAdapter(),
appSyncAdapter({
schemaFile: 'schema.graphql',
generateCrud: {models: 'all'},
}),
],
})Use this if you want to hack on simple-cdk itself or run the bundled examples.
git clone https://github.com/pujaaan/simple-cdk.git
cd simple-cdk
npm install
npm run buildEach adapter follows the same pattern: drop files in a folder, the adapter discovers them, the engine turns them into CDK resources.
Auto-discovers Lambda handlers from backend/functions/.
backend/functions/
hello/
handler.ts # required, exports `handler`
config.ts # optional, per-function options
lambdaAdapter({
dir: 'backend/functions', // default
defaultMemoryMb: 256,
defaultTimeoutSeconds: 30,
stackName: 'lambda', // default
})Per-function config.ts lets you override memory, timeout, runtime, environment, and IAM policies. Defaults: nodejs20.x, 256 MB, 30 s.
Model-driven DynamoDB tables from backend/models/*.model.ts.
// backend/models/todo.model.ts
import type {DynamoDbModelConfig} from '@simple-cdk/dynamodb'
export default {
pk: {name: 'id'},
// sk, gsis, stream, ttlAttribute, billingMode all supported
} satisfies DynamoDbModelConfigdynamoDbAdapter({
dir: 'backend/models', // default
match: ['.model.ts', '.model.js'], // default
stackName: 'data', // default
})Defaults: PAY_PER_REQUEST, point-in-time recovery on. Table name: <app>-<stage>-<model>.
Declare streamTargets on a model to wire the table's stream to one or more Lambda consumers (the adapter attaches a DynamoEventSource in the wire phase):
export default {
pk: {name: 'id'},
streamTargets: ['on-todo-change'],
streamTargetOptions: {startingPosition: 'LATEST', batchSize: 50},
} satisfies DynamoDbModelConfigGraphQL API from a schema file. Auto-generates CRUD resolvers for any DynamoDB model and exposes a pluggable auth pipeline.
appSyncAdapter({
schemaFile: 'schema.graphql', // required
apiName: 'api', // default
authorization: {kind: 'api-key'}, // or 'iam' | 'cognito'
generateCrud: {
models: 'all', // or ['todo', 'user']
operations: ['get', 'list', 'create', 'update', 'delete'],
softDelete: false,
},
resolvers: [
// manual resolvers for anything CRUD doesn't cover
{
typeName: 'Query',
fieldName: 'hello',
source: {kind: 'lambda', lambdaName: 'hello'},
},
],
authPipeline: {jsFile: 'resolvers/auth.js'}, // optional
})Runs in the wire phase, so it can see Lambdas and tables registered by other adapters.
Cognito user pool + Lambda triggers from backend/triggers/.
backend/triggers/
pre-sign-up/handler.ts
post-confirmation/handler.ts
cognitoAdapter({
poolName: 'users', // default
triggersDir: 'backend/triggers', // default
signInAlias: 'email', // default
selfSignUp: true, // default
mfa: 'off', // 'optional' | 'required'
// standardAttributes, customAttributes, passwordPolicy supported
})Folder names map to Cognito triggers: pre-sign-up, post-confirmation, pre-authentication, post-authentication, pre-token-generation, custom-message, define-auth-challenge, create-auth-challenge, verify-auth-challenge, user-migration.
A single RDS instance (Postgres or MySQL) with a VPC (isolated subnets, no NAT gateway by default) and a managed Secrets Manager secret.
import {rdsAdapter, getRdsInstance} from '@simple-cdk/rds'
rdsAdapter({
engine: 'postgres', // or 'mysql' — required
instanceClass: 't4g.micro', // default
allocatedStorageGb: 20, // default
multiAz: false, // default
publiclyAccessible: false, // default
stackName: 'data', // default
})No automatic IAM or network wiring — grant Lambdas access explicitly in a wiring adapter (db.connections.allowDefaultPortFrom(fn); db.secret!.grantRead(fn)). Lookups: getRdsInstance, getRdsSecret, getRdsVpc, getRdsSecurityGroup.
Bundles values from your stacks into one SSM String parameter so a frontend can fetch the whole config object in one call.
import {outputsAdapter} from '@simple-cdk/outputs'
import {getUserPool} from '@simple-cdk/cognito'
import {getAppSyncApi} from '@simple-cdk/appsync'
outputsAdapter({
collect: (ctx) => ({
userPoolId: getUserPool(ctx).userPoolId,
graphqlUrl: getAppSyncApi(ctx).graphqlUrl,
region: ctx.config.stageConfig.region,
}),
// parameterName defaults to `/<app>/<stage>/outputs`
// cfnOutputs: true (default) — also emit each key as a CfnOutput
})Runs in the wire phase, so every other adapter's resources are already registered. Token values like pool.userPoolId resolve at deploy time.
| Command | What it does |
|---|---|
simple-cdk list |
Run discovery and print what each adapter found. No synth, no deploy. |
simple-cdk create <kind> <name> |
Scaffold a model, function, or trigger. |
simple-cdk generate-schema |
Emit schema.graphql from discovered DynamoDB models. |
simple-cdk synth |
Generate CloudFormation. |
simple-cdk diff |
Diff against the deployed stack. |
simple-cdk deploy |
Push to AWS. |
simple-cdk destroy |
Tear down stacks. |
Flags: --stage <name> to pick a stage (default: defaultStage in config). Anything after -- is forwarded to the underlying cdk CLI:
simple-cdk deploy --stage prod -- --require-approval never --concurrency 4Adapters are plain objects matching the Adapter interface. The engine doesn't care where they come from. Built-in or yours, they're treated the same.
Pass your own object instead of the factory result. Same name replaces the built-in:
import {lambdaAdapter} from '@simple-cdk/lambda'
import {Tags} from 'aws-cdk-lib'
const base = lambdaAdapter()
const taggedLambda = {
...base,
register: async (ctx) => {
await base.register?.(ctx)
for (const r of ctx.resources) {
const fn = (r.config as any).construct
if (fn) Tags.of(fn).add('Owner', 'platform')
}
},
}
export default defineConfig({
app: 'my-app',
stages: {dev: {region: 'us-east-1'}},
adapters: [taggedLambda],
})Anything not covered by a built-in adapter? Write one. It's a small interface: three optional hooks and a name.
// 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:
import {sqsAdapter} from './adapters/sqs.js'
adapters: [lambdaAdapter(), sqsAdapter({queues: ['orders', 'emails']})]The three hooks:
discover(ctx): find what you're responsible for (scan files, read config, hit an API). Return a list ofResourceobjects.register(ctx): turn each resource into a CDK construct. Usectx.stack(name)to get-or-create a stack.wire(ctx): runs after every adapter'sregister. Usectx.resourcesOf('lambda')to look up other adapters' resources and connect them.
All three are optional. See docs/Extending.md for a complete adapter walkthrough including filesystem discovery, cross-adapter wiring, and the AppSync auth pipeline.
- Getting Started for install, configure, deploy
- Architecture for engine, adapters, lifecycle
- Extending to override or write adapters
Pre-1.0. APIs may change. Issues and PRs welcome at github.com/pujaaan/simple-cdk.
MIT
https://dev.to/pujaaan/i-got-tired-of-writing-the-same-cdk-wiring-so-i-built-simple-cdk-obg