Why I Built My Own AWS Deployment Tool
Source: Dev.to
My Journey to Building a Faster Serverless Deployment Tool
I’m a software engineer with 12+ years of experience across many languages, teams, and dozens of projects. I love what I do, and I’m drawn to solving real problems with clean tools.
The Problem
- Serverless on AWS – I’ve spent years building server‑less applications using Serverless Framework and AWS CDK.
- Friction – The gap between having an idea and getting it running in the cloud felt wider than it should be.
- CloudFormation bottlenecks – Every deployment required waiting for CloudFormation to diff, plan, and roll out changes, even for a one‑line code fix. For serverless, where agility is the point, this felt like dragging an anchor.
Inspiration from Other Clouds
- Google Cloud / Firebase – I was impressed by Firebase’s developer experience: describe functions in code, run a single CLI command, and everything is deployed.
- Why AWS still wins – AWS Lambda felt simpler (no container builds, fast cold‑starts) and matched my mental model better than the “containers everywhere” approach of Google Cloud.
The Idea
“Combine Firebase’s code‑as‑config model with direct AWS SDK calls, skipping CloudFormation entirely.”
If I could write a Lambda handler, declare its configuration right next to the code, and run a single command to deploy everything—no CloudFormation templates, no stack definitions, no bundler config, no IAM boilerplate—then I’d have the fast feedback loop I wanted.
Core Challenges
- State reconciliation – Replicate what CloudFormation does: compare the current AWS state with the desired state and sync the difference, but using only direct SDK calls.
- Type‑safe orchestration – Manage API calls, errors, and concurrency without the codebase spiralling out of control.
The Three Libraries That Made It Possible
| Library | Why It Helped |
|---|---|
| ts‑morph | Analyzes TypeScript AST, letting me extract infrastructure information directly from handler code (no separate YAML/JSON). |
| Effect‑TS | Provides a functional effect system for orchestrating API calls, handling errors, and managing concurrency with strong type safety. |
| Typed AWS SDK wrapper (my own) | Generates Effect‑wrapped SDK calls with fully typed error handling, based on JSDoc from the AWS SDK source. |
With these tools I built effortless‑aws.
Design Goals
- Single‑object configuration – Every handler is a single function call that takes one options object (
path,method,handler logic,dependencies,parameters, etc.). No curried functions or builder chains. - Context & DI via Effect‑TS – Handlers declare a context factory at the Lambda level; it’s injected into every request handler with compile‑time guarantees that all dependencies are satisfied.
- Type‑checked dependencies – Handlers that need other resources (e.g., a DynamoDB table) simply add a
depsfield; the system wires everything up automatically.
Minimal Handler Example
import { defineHttp } from "effortless-aws";
export const hello = defineHttp({
method: "GET",
path: "/hello",
onRequest: async ({ req }) => ({
status: 200,
body: { message: "Hello World!" },
}),
});
That’s the Lambda function, its API‑Gateway route, and its IAM role—all in one file.
Run eff deploy and it’s live.
Comparison: CDK vs. Effortless‑AWS
CDK – Creating a Simple HTTP Endpoint with a DynamoDB Table
// CDK: stack definition (separate file)
const table = new dynamodb.Table(this, "Orders", {
partitionKey: { name: "id", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});
const fn = new nodejs.NodejsFunction(this, "GetOrders", {
entry: "src/handlers/get-orders.ts",
runtime: lambda.Runtime.NODEJS_20_X,
environment: { TABLE_NAME: table.tableName },
bundling: { /* … */ },
});
- Multiple files – Stack definition, function definition, and bundling config are all separate.
- CloudFormation – Deployments wait for diff/plan/execute cycles.
- Boilerplate – IAM roles, permissions, and resource references must be wired manually.
Effortless‑AWS – Same Outcome in One Place
import { defineHttp } from "effortless-aws";
export const getOrders = defineHttp({
method: "GET",
path: "/orders",
deps: {
table: {
name: "Orders",
partitionKey: "id",
billingMode: "PAY_PER_REQUEST",
},
},
onRequest: async ({ deps }) => {
const items = await deps.table.scan().items();
return { status: 200, body: items };
},
});
- Single file – Handler, API route, DynamoDB table, and IAM permissions are declared together.
- No CloudFormation – Direct SDK calls make deployment near‑instant.
- Zero boilerplate – The framework infers IAM policies and wiring from the
depsobject.
Takeaways
- Skipping CloudFormation for serverless workloads can shave minutes (or even seconds) off the feedback loop.
- A code‑as‑config approach, powered by TypeScript AST analysis and a strong effect system, gives you the developer experience of Firebase on AWS.
- With proper type‑level guarantees, you can keep the flexibility of raw SDK calls without the chaos of manual wiring.
That’s the story of why I finally snapped and built my own deployment tool – effortless‑aws – and how it solves the pain points I faced with Serverless Framework, CDK, and CloudFormation.
Infrastructure with CDK (for comparison)
fy: true, sourceMap: true },
});
table.grantReadData(fn);
const api = new apigateway.HttpApi(this, "Api");
api.addRoutes({
path: "/orders",
methods: [apigateway.HttpMethod.GET],
integration: new HttpLambdaIntegration("GetOrdersIntegration", fn),
});
That’s just the infrastructure. You still need the handler file, the stack wiring, the app entry point, and cdk deploy with CloudFormation.
The same thing with effortless‑aws
“One file. One command.”
// That's it. One file.
import { defineHttp, defineTable } from "effortless-aws";
export const orders = defineTable({
pk: "id",
});
export const getOrders = defineHttp({
method: "GET",
path: "/orders",
deps: { orders },
onRequest: async ({ deps }) => {
const items = await deps.orders.scan();
return { status: 200, body: items };
},
});
Run:
eff deploy
Done. The table, the function, the route, the IAM permissions — all created in seconds.
How eff deploy works
eff deploy goes through four stages. Orchestrating them was the hardest part of the project — each stage feeds into the next, errors can happen anywhere, and resources depend on each other. Effect‑TS made this manageable: the whole pipeline is composable, each step is an Effect you can reason about independently.
1. Code analysis (ts‑morph)
ts‑morphreads your TypeScript source and extracts everydefineHttp/defineTablecall — method, path, deps, params, static files — straight from the AST.- This replaces separate YAML/JSON config files; the code itself becomes the source of truth.
2. Bundling (esbuild)
esbuildcompiles each handler into a single ESM file.- To avoid bundling the same
node_modulesinto every Lambda ZIP, shared dependencies are placed in a Lambda Layer (one layer for the whole project). - Each handler bundles into a clean single JS file; the layer provides
node_modulesat runtime. - Works reliably with pnpm projects so far.
3. State comparison
- The tool checks what already exists in AWS and compares it with what your code declares.
- Only the differences are applied – essentially what CloudFormation does, but without the provisioning‑engine overhead.
- The biggest conceptual challenge was finding the right granularity of comparison and ensuring idempotent updates.
4. Resource provisioning (AWS SDK + Effect‑TS)
- Direct AWS SDK calls create, update, or reconfigure resources (Lambda functions, DynamoDB tables, API Gateway routes, IAM policies).
- Every AWS call goes through typed Effect wrappers, so every possible error is known.
- If a function already exists → log and continue.
- If an internal AWS error occurs → stop the deploy.
Project status
- effortless‑aws is currently alpha.
- It’s already used in production for:
- The documentation website.
- Personal projects that use Lambda functions, DynamoDB tables, streams, and triggers.
- Everything deploys and runs, but rough edges remain and active development continues.
Roadmap
- New handlers (e.g., S3 static websites, EventBridge).
- Improved diff algorithm.
- Better TypeScript ergonomics.
Installation & Resources
npm install effortless-aws
- GitHub:
- Website & Docs:
Tools used to build the project
| Tool | Purpose |
|---|---|
| Effect‑TS | Typed functional programming for TypeScript |
| ts‑morph | TypeScript AST analysis and manipulation |
| esbuild | Fast JavaScript/TypeScript bundler |
| AWS SDK for JavaScript (v3) | Official AWS SDK |
| Typed AWS SDK wrapper | Effect wrappers with typed errors for AWS SDK |