Web Development

AWS Blocks: Full-Stack Building Blocks That Run Locally Without an AWS Account

June 17, 2026 2 min read

Why I built a Custom Kiro Power to ship faster with AWS Blocks

Every developer building on AWS has hit the same wall. You need a sandbox account to test your idea. In most organizations that means a ticket, an approval workflow, a budget tag, and three days of waiting before you can find out if your DynamoDB schema even makes sense.

Once you get access, the iteration cycle starts: write code, deploy, wait two minutes, request pull request approvals from Cloud Engineering/Platform leads and discover your IAM policy is wrong, fix it, deploy again, wait again. For a single table and an API endpoint, you might burn half a day just getting to "hello world" on real infrastructure.

And when the sprint is over, somebody needs to tear it all down so the bill doesn't keep running.

AWS Blocks eliminates this entire loop. You write your application using self-contained building blocks that run locally on your laptop with zero AWS credentials, then flip a single switch to deploy real infrastructure when the feature is validated. No sandbox requests. No deploy-wait-fix cycles during development. No environment-specific configuration. The code you test locally IS the code that runs on AWS.

How AWS Blocks works

You import a building block and use it directly in your application logic. There is no separate infrastructure definition layer. AWS calls this "Infrastructure from Code" — your backend entry point (aws-blocks/index.ts) is both your runtime code and your infrastructure definition simultaneously.

import { Scope, ApiNamespace, DistributedTable } from '@aws-blocks/blocks';
import { z } from 'zod';

const scope = new Scope('my-app');

const tasks = new DistributedTable(scope, 'tasks', {
  schema: z.object({
    userId: z.string(),
    taskId: z.string(),
    title: z.string(),
    done: z.boolean(),
  }),
  key: { partitionKey: 'userId', sortKey: 'taskId' },
});

export const api = new ApiNamespace(scope, 'api', (context) => ({
  async addTask(userId: string, title: string) {
    await tasks.put({ userId, taskId: crypto.randomUUID(), title, done: false });
  }
}));

Run npm run dev and this works immediately. The table persists to .bb-data/ on disk (survives restarts). The API serves on localhost with hot reload. Your frontend imports the backend directly with full type safety — no codegen, no REST clients, no OpenAPI specs:

You write Runs locally as Deploys to AWS as
new DistributedTable(scope, 'orders', { schema, key, indexes }) JSON file on disk DynamoDB with GSIs
new Database(scope, 'analytics', { migrationsPath }) PGlite (WASM Postgres) Aurora Serverless v2