Why I Built My Own AWS Deployment Tool

Published: (February 17, 2026 at 12:40 PM EST)
7 min read
Source: Dev.to

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

  1. 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.
  2. Type‑safe orchestration – Manage API calls, errors, and concurrency without the codebase spiralling out of control.

The Three Libraries That Made It Possible

LibraryWhy It Helped
ts‑morphAnalyzes TypeScript AST, letting me extract infrastructure information directly from handler code (no separate YAML/JSON).
Effect‑TSProvides 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 deps field; 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 deps object.

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‑morph reads your TypeScript source and extracts every defineHttp / defineTable call — 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)

  • esbuild compiles each handler into a single ESM file.
  • To avoid bundling the same node_modules into 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_modules at 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

ToolPurpose
Effect‑TSTyped functional programming for TypeScript
ts‑morphTypeScript AST analysis and manipulation
esbuildFast JavaScript/TypeScript bundler
AWS SDK for JavaScript (v3)Official AWS SDK
Typed AWS SDK wrapperEffect wrappers with typed errors for AWS SDK
0 views
Back to Blog

Related posts

Read more »

Getting Started with AWS EC2 Key Pairs

Introduction Hey there, cloud enthusiasts! If you're just starting your AWS journey like I am, you've probably heard the term “key pairs” thrown around and won...