Skip to content

Comparisons

Why Not CloudFormation?

The Problem with Abstraction Layers

Most AWS deployment tools follow this pattern:

Your Code → YAML/JSON Config → CloudFormation → AWS API

Each layer adds:

  • Latency: CloudFormation stack operations take minutes, even for simple changes
  • Complexity: Debugging requires understanding multiple abstraction levels
  • State drift: Template state can diverge from actual AWS state
  • Vendor lock-in: Your infrastructure is tied to CloudFormation’s model

Effortless approach: Direct AWS SDK Calls

Your Code → AWS SDK → AWS API

No intermediary. Effortless calls the same APIs that CloudFormation calls, but directly.

Speed Comparison

OperationCloudFormationEffortless
Create Lambda60-120s5-10s
Update Lambda code30-60s3-5s
Create API Gateway60-90s2-3s
Full redeploy5-10 min30-60s

CloudFormation is slow because:

  1. It validates the entire stack template
  2. It creates a change set
  3. It executes changes sequentially with rollback capability
  4. It waits for each resource to stabilize

Effortless is fast because:

  1. Direct API calls
  2. Independent operations run in parallel
  3. ensure* pattern — check existence, create or update

Imperative vs Declarative

CloudFormation (declarative):

“Here’s my desired state. Figure out how to get there.”

Effortless (imperative):

“Check if this exists. If not, create it. If yes, update it.”

Declarative sounds elegant, but requires:

  • Complex diffing algorithms
  • State tracking and reconciliation
  • Rollback mechanisms
  • Dependency graph resolution

The imperative approach is:

  • Predictable - you know exactly what will happen
  • Debuggable - each API call is visible
  • Fast - no diff calculation, no rollback overhead
  • Simple - ~50 lines per resource type vs thousands in CDK constructs

Tags as State (Not Files)

Traditional tools store state in:

  • Local files (Terraform tfstate)
  • S3 buckets (Terraform remote backend)
  • DynamoDB (CDK)
  • CloudFormation stacks

Problems:

  • State can drift from reality
  • Team coordination requires locking
  • Lost state = orphaned resources or duplicates
  • Another thing to manage and back up

Effortless approach: AWS tags ARE the state.

effortless:project = my-app
effortless:stage = dev
effortless:handler = orders
effortless:type = lambda

Benefits:

  • AWS is always the source of truth
  • No sync issues - query AWS, get current state
  • Works in teams without locking
  • State survives across machines, CI environments
  • Resource Groups Tagging API: one call to find all resources

Trade-off: Resource Groups Tagging API has indexing delay (~1-2 minutes). For new resources, Effortless uses direct API responses. Tags are for discovery of existing resources.


Why Not Terraform/Pulumi?

Excellent tools, but:

  1. Separate language/DSL - HCL, YAML, or their SDK
  2. State management overhead - backends, locking, imports
  3. General purpose - designed for any cloud, any resource
  4. Learning curve - significant investment to master

Effortless is:

  1. TypeScript only - one language for infra and code
  2. Stateless - tags + API queries
  3. Focused - Lambda ecosystem only, but done well
  4. Zero config - export a function, get infrastructure

Why Not SST?

SST v3 moved from CloudFormation to Pulumi/Terraform — faster deploys and multi-cloud support. It has a solid Console (logs, errors, team management). It’s the closest mainstream competitor.

But SST is still infrastructure AS code, not FROM code:

// SST — you define infrastructure explicitly in sst.config.ts
const table = new sst.aws.Dynamo("Orders", {
fields: { id: "string" },
primaryIndex: { hashKey: "id" },
});
// then link it to your function
new sst.aws.Function("Api", {
handler: "src/api.handler",
link: [table],
});
// then in your handler, you use the SDK manually
import { Resource } from "sst";
const tableName = Resource.Orders.name;
// ...raw DynamoDB SDK calls, no typed client
// Effortless — infrastructure IS the code
export const orders = defineTable({
schema: unsafeAs<{ id: string; amount: number }>(),
primaryKey: "id",
onInsert: async ({ newItem }) => {
// typed: newItem.amount is number
},
});
// In another handler — typed client, auto IAM, no config
await orders.put({ id: "abc", amount: 99 });

Key differences:

SST v3Effortless
Infrastructure definitionSeparate config file (sst.config.ts)Export handlers from app code
Typed client from schemaNo — raw SDK, manual typesYes — defineTable → typed .put(), .get()
State managementPulumi state (S3 + lock)No state files — AWS tags
Deployment enginePulumi/TerraformDirect AWS SDK calls
DashboardYes (SST Console)Planned (control plane + web UI)
Live dev (local proxy)Yes (sst dev)No — fast redeploys (~5s)
Deploy speed~30s (Pulumi diff)~5-10s (direct API calls)

SST’s sst dev proxies Lambda invocations to your local machine, so you can set breakpoints and hot-reload without redeploying. Effortless takes a different stance: local mocks (in-memory DynamoDB, fake IAM) create false confidence — “works locally, breaks on AWS” is a real problem. Instead, Effortless relies on fast direct deploys (~5s) and encourages writing tests for correctness. If you’re unsure something works, a test catches it reliably; manual localhost poking does not.

SST is great for teams already comfortable with IaC who want better DX. Effortless is for teams who don’t want to write infrastructure code at all.


Why Not Nitric?

Nitric is the closest to effortless philosophically — true infrastructure-from-code where you write app code and infrastructure is inferred.

But Nitric trades depth for breadth:

// Nitric — multi-cloud, but generic API
import { api, collection } from "@nitric/sdk";
const orders = collection("orders").for("writing");
api("main").post("/orders", async (ctx) => {
await orders.doc("abc").set({ amount: 99 });
// no schema validation, no typed fields
// works on AWS, GCP, Azure — but lowest common denominator
});
// Effortless — AWS-native, schema-driven
export const orders = defineTable({
schema: unsafeAs<{ id: string; amount: number }>(),
primaryKey: "id",
});
// orders.put() is typed, validated, auto-IAM'd
NitricEffortless
Multi-cloudYes (AWS, GCP, Azure)No — AWS only
Typed clients from schemaNoYes
Schema validation at runtimeNoYes
Deployment enginePulumi/TerraformDirect AWS SDK
Own runtime/SDKYes (gRPC sidecar)No — native AWS Lambda
State filesYes (Terraform/Pulumi state)No — AWS tags
AWS-specific featuresLimited (lowest common denominator)Full (DynamoDB streams, API Gateway features, etc.)

Nitric is better if you need multi-cloud. Effortless is better if you’re on AWS and want deep integration, type safety, and zero abstraction overhead.


Why Not Ampt?

Ampt (spun off from Serverless Cloud) does infrastructure-from-code with sub-second sandbox deploys.

But Ampt is a managed platform — you’re locked into their service:

AmptEffortless
Runs in your AWS accountNo — Ampt-managedYes — your account, your control
Vendor dependencyAmpt service must existNone — direct AWS SDK
Open sourceNoYes
PricingPer-invocation (Ampt pricing)AWS costs only
PortabilityLocked to AmptStandard AWS Lambda

If Ampt shuts down, your app needs rewriting. Effortless deploys standard AWS resources — your app runs with or without effortless.


Why Not Alchemy?

Alchemy is a TypeScript-native IaC library that provisions cloud resources (Cloudflare, AWS, GitHub) via direct HTTPS calls. No YAML, no CloudFormation — just TypeScript.

But Alchemy is infrastructure-AS-code, not FROM-code:

// Alchemy — you explicitly declare resources
const db = await Database("my-db");
const bucket = await Bucket("assets", {
bucketName: `my-bucket-${stage}`
});
const worker = await Worker("api", {
bindings: { DB: db }
});
// You still manage resources manually — Alchemy just makes the language nicer
// Effortless — infrastructure IS the code
export const orders = defineTable({
schema: unsafeAs<{ id: string; amount: number }>(),
primaryKey: "id",
});
export const api = defineApi({
basePath: "/orders",
deps: () => ({ orders }), // ← auto IAM, typed client
get: {
"/": async ({ deps }) => {
const items = await deps.orders.query(/* fully typed */);
return { status: 200, body: items };
},
},
});
// No resource declarations — Lambda, IAM, Function URL all inferred
AlchemyEffortless
ApproachInfrastructure-as-code (TS instead of YAML)Infrastructure-from-code (inferred from handlers)
Abstraction levelMedium — explicit resource declarationsHigh — define* → everything
Typed runtime clientsNo — provisioning onlyYes — TableClient<T>, auto IAM
State managementLocal state files (like Terraform)No state files — AWS tags
Multi-cloudYes (Cloudflare, AWS, GitHub)No — AWS only
Runtime wrappersNo — you write Lambda handlers yourselfYes — wrappers, DI, context caching
Deployment engineDirect HTTPS to cloud APIsDirect AWS SDK calls
AI-firstYes — generate resources via LLMsNo — but resources are inferred, so nothing to generate

Alchemy competes with Pulumi and CDK — it makes IaC nicer by replacing YAML with TypeScript. Effortless eliminates IaC entirely: you write handlers, infrastructure follows.

Alchemy is a good fit if you need multi-cloud provisioning in TypeScript. Effortless is for teams on AWS who don’t want to write infrastructure code at all.


Why Not Serverless Framework?

Serverless Framework v3+ builds on CloudFormation:

Serverless Framework → CloudFormation → AWS

It inherits CloudFormation’s slow deployments, stack limits (500 resources), complex error messages, and rollback behavior. You also write YAML config (serverless.yml) separately from your code.

Effortless bypasses all of this with direct SDK calls and zero config files.


Comparison Summary

AspectSST v3NitricAlchemyAmptEffortless
Infra from code (not config)NoYesNoYesYes
Typed client from define*NoNoNoNoYes
Schema → validation + typesNoNoNoNoYes
Auto IAM wiringYes (linking)YesNoYesYes
No state filesNoNoNoN/AYes
Runs in your AWS accountYesYesYesNoYes
Dashboard / ConsoleYesYesNoYesPlanned
Multi-cloudPartialYesYesNoNo
Deploy speed~30s~30sFast<1s~5-10s
Open sourceYesYesYesNoYes

What only effortless does: one define* call creates the AWS resource, generates a typed runtime client, wires IAM permissions, and validates data with the schema — all from a single TypeScript export. No config files, no state files, no separate infrastructure definitions.

Philosophy: Direct is better than indirect. Fast is better than safe-but-slow. Simple is better than complete.