Skip to content

Effortless AWS

Effortless AWS Deploys in ~6 seconds API, database, queues — one deploy No YAML. No state files. No bootstrapping. Export. Deploy. Done.

The fastest way to ship a TypeScript app onto AWS.

Ship the whole product

API, database, queues, email, website — one project, one deploy, automatic IAM wiring.

Deploy in seconds

Direct AWS API calls. No CloudFormation, no state files. Full deploy in ~5-10 seconds.

Type safety everywhere

Define a table once — get typed clients, typed streams, and runtime validation automatically.

Serverless by default

Pay per use, scale to zero, multi-AZ redundancy. Nothing runs when nobody is using your product.

Everything a product needs

Your product needsEffortless handlerAWS resources created
REST APIdefineApiLambda + Function URL + IAM
DatabasedefineTableDynamoDB + optional stream Lambda
Background jobsdefineQueueSQS FIFO + consumer Lambda
File storagedefineBucketS3 + optional event Lambda
Transactional emaildefineMailerSES + DKIM identity
Website / SSR appdefineAppCloudFront + Lambda + S3
Static site / SPAdefineStaticSiteCloudFront + S3

All in the same project, all deployed with one command, all with automatic IAM wiring between them.

All definitions

What it replaces

A typical AWS app splits this into two layers: an infrastructure description (CDK, Terraform, Pulumi…) and a separate handler using the raw AWS SDK. Effortless collapses both into one file.

One file. Table and API live together. IAM, table name, and Function URL are derived from .deps() automatically.

src/api.ts
import { defineTable, defineApi } from "effortless-aws";
export const todos = defineTable<{ tag: "todo"; title: string }>().build();
export const api = defineApi({ basePath: "/todos" })
.deps(() => ({ todos }))
.setup(({ deps }) => ({ todos: deps.todos }))
.get({ path: "/" }, async ({ todos }) => ({
status: 200,
body: await todos.query({ pk: "TODO" }),
}));
Terminal window
$ eff deploy # ~6s, direct AWS API calls

Same logical operations. Effortless gives you a typed table client, derives IAM from code, and skips the IaC layer entirely.

Build a todo app

Two files, two handlers. db.ts becomes a DynamoDB table with a stream Lambda; api.ts becomes one Lambda behind a Function URL serving all the routes.

This 9-line file becomes a DynamoDB table, a stream Lambda that runs on every change, and the IAM role linking them. The typed client (todos.query, todos.put, todos.update) is derived from Todo.

src/db.ts
import { defineTable } from "effortless-aws";
export type Todo = { tag: "todo"; id: string; title: string; done: boolean };
export const todos = defineTable<Todo>()
.onRecord(async ({ record }) => {
if (record.eventName === "MODIFY" && record.new?.data.done) {
console.log(`Todo completed: ${record.new.data.title}`);
}
});

Deploy

Terminal window
$ eff deploy
Deployed 2 handler(s) in 6s:
[table] todos arn:aws:dynamodb:eu-west-1:***:table/todo-app-dev-todos
[api] todoApi https://abc123.lambda-url.eu-west-1.on.aws/todos

Need a separate environment? Add --stage:

Terminal window
$ eff deploy --stage prod
Deployed 2 handler(s) in 6s:
[table] todos arn:aws:dynamodb:eu-west-1:***:table/todo-app-prod-todos
[api] todoApi https://xyz789.lambda-url.eu-west-1.on.aws/todos

Fully isolated infrastructure — separate tables, Lambdas, Function URLs. No shared state between stages.

See how Effortless compares to SST, Nitric, Serverless Framework, and others — detailed comparisons.