AWS Blocks
Write your backend and frontend together — fully typed, runnable on your laptop, deployable to AWS unchanged.
AWS Blocks is a backend framework built from Building Blocks: self-contained modules that each bundle a CDK construct, its AWS SDK integration, and a local mock. You compose blocks in one directory, export an API, and call it from your frontend with end-to-end type safety. No client generation, no glue code, no AWS account needed to start.
This package (@aws-blocks/blocks) re-exports every Building Block and the core primitives, so you import everything from one place:
import { Scope, ApiNamespace, KVStore, AuthBasic } from '@aws-blocks/blocks';
- Type-safe, end to end — your frontend calls backend methods directly; types flow through automatically.
- Local-first — every block runs as an in-memory mock, so you build and test with zero cloud setup.
- Deploys unchanged —
npm run sandboxswaps the mocks for real AWS services (DynamoDB, Aurora, S3, Lambda…). Same code. - Low ceremony, high ceiling — common things are one line; when you need the underlying CDK construct or AWS SDK, it's right there.
Quick Start
npx @aws-blocks/create-blocks-app my-app
cd my-app
npm run dev # → http://localhost:3000 (mocks, no AWS account needed)
--template <name> picks a starter (react, nextjs, backend, …); see @aws-blocks/create-blocks-app.
How it works
Your entire backend lives in one directory, aws-blocks/. You create blocks, then expose methods through an ApiNamespace:
// aws-blocks/index.ts
import { Scope, ApiNamespace, KVStore } from '@aws-blocks/blocks';
const scope = new Scope('my-app');
const store = new KVStore(scope, 'cache');
export const api = new ApiNamespace(scope, 'api', (context) => ({
async getValue(key: string) {
return await store.get(key);
},
async setValue(key: string, value: string) {
await store.put(key, value);
},
}));
The frontend imports that API and calls it like a local function — fully typed, no fetch, no client codegen:
// src/
import { api } from 'aws-blocks';
await api.setValue('greeting', 'hello');
const value = await api.getValue('greeting'); // typed: string | null
That's the whole model: define blocks → export an API → import it on the frontend. The transport (JSON-RPC under /aws-blocks/api) is handled for you and is intentionally invisible.
Adding auth and data
Blocks compose. Here's the same API gated behind authentication and backed by a queryable table:
// aws-blocks/index.ts
import { Scope, ApiNamespace, AuthBasic, DistributedTable } from '@aws-blocks/blocks';
import { z } from 'zod';
const scope = new Scope('my-app');
const auth = new AuthBasic(scope, 'auth', { passwordPolicy: { minLength: 8 } });
const notes = new DistributedTable(scope, 'notes', {
schema: z.object({ userId: z.string(), noteId: z.string(), text: z.string() }),
key: { partitionKey: 'userId', sortKey: 'noteId' },
});
// Sign-up / sign-in endpoints, ready to wire to the frontend
export const authApi = auth.createApi();
export const api = new ApiNamespace(scope, 'api', (context) => ({
async addNote(text: string) {
const user = await auth.requireAuth(context); // 401s if not signed in
const noteId = crypto.randomUUID();
await notes.put({ userId: user.username, noteId, text });
return { noteId };
},
async listNotes() {
const user = await auth.requireAuth(context);
return await Array.fromAsync(notes.query({ where: { userId: { equals: user.username } } }));
},
}));
Security: every
ApiNamespacemethod is a public internet endpoint with no auth by default. Gate a method by callingauth.requireAuth(context)(orauth.requireRole(...)) at the top. The local mock enforces nothing either — an ungated method passes every local check and still ships callable by anyone.
On the frontend, @aws-blocks/blocks/ui gives you provider-agnostic auth components, or drive authApi yourself:
import { Authenticator, onAuthChange } from '@aws-blocks/blocks/ui';
import { authApi, api } from 'aws-blocks';
document.body.append(Authenticator(authApi));
onAuthChange(authApi, (user) => {
if (user) api.listNotes().then(render);
});
Building Blocks
Start from what you need:
- Store data
- Simple key → value (caches, flags, user prefs) →
KVStore(bb-kv-store) - Structured records with indexes and queries →
DistributedTable(bb-distributed-table) — default for most data - Relational / SQL (joins, transactions) → see Choosing a data block below
- Files, blobs, uploads, static assets →
FileBucket(bb-file-bucket) - A single config value →
AppSetting(bb-app-setting) - A high-value credential (API key, OAuth secret, connection string) →
Secret(bb-secret), backed by AWS Secrets Manager
- Simple key → value (caches, flags, user prefs) →
- Authenticate users
- Username/password, prototypes/MVPs →
AuthBasic(bb-auth-basic) - Cognito user pools, MFA, groups →
AuthCognito(bb-auth-cognito) - External identity provider (OIDC) →
AuthOIDC(bb-auth-oidc)
- Username/password, prototypes/MVPs →
- Run work outside the request/response
- Fire-and-forget background jobs →
AsyncJob(bb-async-job) - Scheduled / recurring tasks →
CronJob(bb-cron-job)
- Fire-and-forget background jobs →
- Push live updates to browsers (chat, presence, dashboards) →
Realtime(bb-realtime) - Build AI features
- Agent with tool use + conversation →
Agent(bb-agent) - Semantic document retrieval (RAG) →
KnowledgeBase(bb-knowledge-base)
- Agent with tool use + conversation →
- Send transactional email →
EmailClient(bb-email-client) - Serve a raw HTTP endpoint (webhook receiver, health check, redirect, non-JSON response) →
RawRoute(core); everything else goes throughApiNamespaceRPC - Observe and operate
- Structured logs →
Logger(bb-logger) - Custom metrics →
Metrics(bb-metrics) - Distributed traces →
Tracer(bb-tracer) - Auto CloudWatch dashboard →
Dashboard(bb-dashboard)
- Structured logs →
Choosing a data block
Default to DistributedTable for your data models unless your domain specifically requires SQL engine capabilities.
Reach for one of the SQL blocks when you need to filter or join results across more than one related record, filter models on many dimensions with no preset hierarchy, store large objects, require transactions, or otherwise need the flexibility or familiarity of SQL that NoSQL does not offer.
If you need SQL, prefer DistributedDatabase for Aurora DSQL, which is PostgreSQL wire-protocol compatible; see the Aurora DSQL SQL compatibility documentation for supported SQL features. Use Database specifically when you need a full (more expensive) Postgres implementation with row level security, triggers, procedures, or PL/pgSQL functions; large transactions (more than 3,000 rows); or integration with an existing Postgres database. Note it carries an idle cost at minimum 0.5 ACU, or a cold start when scaling from zero, unlike the other two blocks.
Building Block documentation
Every Building Block ships its docs inside the @aws-blocks/blocks package under docs/<block>/: each docs/<block>/ folder contains that block's README.md, plus API.md, DESIGN.md, and CHANGELOG.md where present. To read them, locate the bundled folder:
node -p "require('path').dirname(require.resolve('@aws-blocks/blocks/docs/README.md'))"
If resolution fails, fall back to node_modules/@aws-blocks/blocks/docs. That folder holds this guide (README.md), the framework's own API.md, TROUBLESHOOTING.md, and CHANGELOG.md (version history — read when troubleshooting), plus one subfolder per block; the catalog below lists every block.
| Block | What it does | Keywords |
|---|---|---|
| auth-common | Shared interfaces and UI components for all AWS Blocks auth Building Blocks. | — |
| bb-agent | AI agent with streaming, tool calling, and conversation persistence. | — |
| bb-app-setting | A single application configuration value backed by SSM Parameter Store. | — |
| bb-async-job | Background job processing backed by SQS, run on Lambda or a container. | queue, job, background, async, worker, submit, batch, retry, status, transitions, SQS |
| bb-auth-basic | Simple username/password authentication with JWT sessions, password policy, and optional code-confirmed signup and password reset. | — |
| bb-auth-cognito | Authentication backed by Amazon Cognito User Pools. | — |
| bb-auth-oidc | OIDC sign-in gate for AWS Blocks applications. | — |
| bb-compute | The Compute Building Block is the single surface for choosing where a workload runs. | — |
| bb-cron-job | Scheduled task execution backed by EventBridge Scheduler and Lambda. | cron, schedule, timer, periodic, recurring, rate, EventBridge, background, interval |
| bb-dashboard | Auto-generated CloudWatch Dashboard for application observability. | — |
| bb-data | Full PostgreSQL database — provisions Aurora Serverless v2 by default, or connects to an existing PostgreSQL database (Supabase, Neon, etc.) via fromExisting(). | — |
| bb-distributed-data | Serverless SQL database backed by Amazon Aurora DSQL. | — |
| bb-distributed-table | Structured data storage backed by DynamoDB with secondary indexes and rich query capabilities. | — |
| bb-email-client | Transactional email sending via Amazon SES. | — |
| bb-file-bucket | File storage backed by Amazon S3. | — |
| bb-knowledge-base | Semantic document retrieval backed by Amazon Bedrock Knowledge Bases. | — |
| bb-kv-store | Simple key-value storage backed by DynamoDB. | — |
| bb-logger | Structured logging with consistent JSON format, log levels, and contextual metadata. | — |
| bb-metrics | Custom application metrics backed by Amazon CloudWatch (via Embedded Metric Format). | — |
| bb-realtime | Real-time pub/sub messaging backed by API Gateway WebSocket + DynamoDB. | — |
| bb-secret | A single application secret backed by AWS Secrets Manager. | — |
| bb-tracer | Distributed tracing backed by AWS X-Ray. | — |
| core | Core primitives for building full-stack applications with the AWS Blocks. | — |
| hosting | Low-level CDK L3 constructs for deploying web applications on AWS | — |
| pipeline | CDK Pipelines-based CI/CD construct for AWS Blocks applications. | — |
Local development and deploying
npm run dev | npm run sandbox | |
|---|---|---|
| Blocks run as | in-memory mocks | real AWS services |
| AWS account | not needed | required |
| Data | persists to .bb-data/ (delete to reset) | lives in AWS |
| Use for | rapid iteration, tests | pre-production validation against real services |
Deploying needs AWS credentials.
npm run devis fully local (no creds).npm run sandboxandnpm run deployprovision real AWS resources, so configure credentials first — e.g.aws login(AWS CLI 2.32.0 or later),aws configure sso+aws sso login, oraws configure(verify withaws sts get-caller-identity). If the credentials are missing or expired, both commands stop before synthesizing and print the sign-in or refresh command for the profile they use. Use least-privilege credentials scoped to the services your blocks deploy — not broadAdministratoraccess.
Before they deploy, npm run sandbox and npm run deploy print the deployment target: the credentials that CDK deploys with (an AWS profile, environment access keys, or container or web identity credentials) and the configured Region. As in CDK, environment access keys are used before AWS_PROFILE unless cdk.json sets a profile. With valid credentials they also show the account name and the AWS account plan (Free or Paid) when those credentials allow account:GetAccountInformation and freetier:GetAccountPlanState. Otherwise they show "not available" and continue; these reads never stop a deploy. When you sign in with the AWS-managed role of the new AWS sign-up experience (a role at the path /managed/ that account-access.amazonaws.com can assume, checked with iam:GetRole), the output also links to https://settings.aws.com/billing for spend limits. In AWS Settings, verify that the project you intend to manage matches the CLI deployment target before viewing or changing its spend limit. If you sign in with aws login, set the profile Region to your project's Region before you sign in (aws configure set region <region> --profile <name>), and run aws login again if you change it later. npm run sandbox:destroy and npm run destroy print only the deployment target.
With verified credentials, npm run sandbox (including npm run sandbox:deploy) and npm run deploy also check that CDK has been bootstrapped in that account and Region: they read the SSM parameter /cdk-bootstrap/<qualifier>/version, where the qualifier is @aws-cdk/core:bootstrapQualifier from the cdk.json context, or CDK's default. When the parameter does not exist, they stop before synthesizing and print the command that bootstraps this account and Region, npx cdk bootstrap aws://<account-id>/<region> --app=. Run it from the project directory; --app= keeps CDK from synthesizing the app first. Bootstrapping is needed once per account and Region, and it creates the CDKToolkit CloudFormation stack, including IAM roles that CDK deploys with, so the commands never bootstrap for you. Any other result of the read, such as a denied read or a timeout, does not stop the deploy.
npm run deploy does a full production deploy; npm run sandbox:deploy deploys the sandbox once and exits (no watch, for scripts and CI); npm run sandbox:destroy tears the sandbox down. The same backend code runs in all three — blocks switch implementations automatically.
Importing @aws-blocks/blocks through its default (Node) entry declares the default compute (LambdaCompute) so the CLI reports it in telemetry. It is folded in only when telemetry reads the registry, after the blocks the app constructed, and an app-constructed LambdaCompute takes precedence. The cdk and browser entries do not declare it.
npm run deploy streams CloudFormation events to stdout as they happen, so npm run deploy | tee deploy.log shows live progress instead of going silent for minutes, and a deploy failure keeps its reason on stderr (that stays the place to grep for why a deploy failed). On POSIX the deploy also survives a stray reap: a single SIGTERM, or any SIGHUP — a closed terminal, a backgrounded npm run deploy & — logs a line and keeps streaming rather than abandoning a stack update CloudFormation is still applying. Press Ctrl-C, or send SIGTERM twice, to stop it. That signal resilience is POSIX-only: Windows has no process groups and no OS-delivered SIGTERM/SIGHUP, so there a kill on the process tree still ends the deploy.
Testing
The fastest loop is calling your API through its typed import in test/e2e.test.ts — no browser, no mocking:
import { test } from 'node:test';
import assert from 'node:assert';
import type { api as ApiType } from 'aws-blocks';
let api: typeof ApiType;
test.before(async () => { api = (await import('aws-blocks')).api; });
test('stores and reads back a value', async () => {
await api.setValue('k', 'v');
assert.equal(await api.getValue('k'), 'v');
});
Run with npm run test:e2e. Write the test first, iterate against mocks until it passes.
Best practices
- Export every API — the frontend can only import what you
exportfromaws-blocks/index.ts. - Validate with schemas — pass a Zod/Valibot schema to data blocks for compile-time and runtime type safety.
- Don't block the request — use
AsyncJobfor anything slow;submit()returns ajobIdimmediately. - Guard against races — use conditional writes (
ifNotExists,ifValueEquals,ifFieldEquals) instead of read-modify-write. - Test locally first — mocks behave like the real service; deploy once it's green.
Common mistakes
- Ungated endpoints — methods are public unless you call
requireAuth/requireRole. The local mock won't catch this for you. - Forgetting to export — an
ApiNamespaceyou don't export is invisible to the frontend. DatabasewhenDistributedTablewould do — Aurora costs more and has cold starts; reach for SQL only when you need it.- Curling REST-style paths — there is no
GET /api/getData. All calls are JSON-RPCPOSTs under/aws-blocks/api; use the typed import instead.
Security Considerations
- Use
await auth.requireAuth(context)in every method that shouldn't be public — ApiNamespace methods are unauthenticated by default - Use
new Secret(scope, id)for API keys and credentials —Secretis the recommended home for production secrets.new AppSetting(scope, id, { secret: true })is a free, encrypted fallback only for a low-value secret. Don't hardcode secret values in source, and don't ship real secrets to a deployed app through plaintext environment variables — set them throughSecret/AppSetting - Always attach a schema to KVStore/AppSetting that accepts user data — the RPC layer validates structure but not business logic
- Do not add broad
*IAM policies — each Building Block already grants least-privilege scoped to its own resources - Never change
blockPublicAccesson FileBucket — serve public files through CloudFront instead - Configure
defaults.allowedOriginsexplicitly for production — avoid wildcards defaults.allowedOriginscovers the API only — a FileBucket whose presigned URLs or file handles are fetched from the browser needs its owncorsRuleslisting your frontend origins (includinghttp://localhost:3000for a local frontend against a sandbox), or uploads/downloads fail with a CORS error once deployed- For cross-domain deployments, pass
crossDomain: trueto auth constructors (enablesSameSite=None; Secure; Partitioned) - Enable
frontDoor: { features: { monitoring: { enabled: true, subscriptions: [new subs.EmailSubscription('[email protected]')] } } }on Hosting for production alerts (subs=aws-cdk-lib/aws-sns-subscriptions; email/URL only; the top-levelmonitoringprop is its deprecated alias) - Add WAF and API Gateway throttling via CDK for public-facing apps — not included by default
- Logger provides serialization safety (circular refs, type coercion) but does NOT redact sensitive content — never pass raw credentials, tokens, or secrets to Logger methods; sanitize context objects before logging
IAM hardening
Every Blocks compute runs as one shared IAM (Identity and Access Management) execution role, and every Building Block grants its permissions onto it. Set defaults.permissionsBoundary to attach a permissions boundary, a ceiling on effective permissions, to every IAM role AWS Blocks creates. Pass the policy's name, its full ARN (required when the policy has a path), or an iam.IManagedPolicy. It's unset in both presets, and unset leaves templates unchanged.
// aws-blocks/index.cdk.ts
export const blocksStack = await BlocksStack.create(app, stackName, {
backendHandlerPath: join(__dirname, 'index.handler.ts'),
backendCDKPath: join(__dirname, 'index.ts'),
defaults: { ...BlocksPresets.production, permissionsBoundary: 'my-app-boundary' },
});
- Report. Every synth, with or without a boundary, writes the shared role's composed policy to
cdk.out/<stack>.blocks-iam.json(<stack>.<backendId>.blocks-iam.jsonfor an embeddedBlocksBackend); it isn't deployed, so CI can diff it to catch permission growth. - Scope. On a
BlocksStackthe boundary covers every role in the stack; on an embeddedBlocksBackendit covers the backend's subtree plus the stack-level CDK (Cloud Development Kit) singleton roles its blocks add. - CDK context. If CDK's
@aws-cdk/core:permissionsBoundarycontext is also set,defaults.permissionsBoundarywins for the roles Blocks creates and synth warns.
Details, including sharing one AWS account across many apps, are in the bundled @aws-blocks/core README (docs/core/README.md), sections "IAM: permissions boundary and the composed-policy report" and "Running many apps in one AWS account (a shared platform)".
VPC Support
AWS Blocks supports running your backend in a VPC for network-level isolation or to connect to VPC-bound resources. You can let Blocks manage endpoint provisioning and security groups automatically, or bring a fully-configured VPC and handle networking yourself.
See VPC.md for usage, configuration options, cost details, and guidance on when a VPC is appropriate.
Reference
- Per-block documentation:
docs/<block>/README.md(overview), plusdocs/<block>/API.md(full API reference) anddocs/<block>/DESIGN.md(architecture & rationale) where present — e.g.docs/bb-distributed-table/README.md. The catalog + decision tree live indocs/README.md. - UI components (
@aws-blocks/blocks/ui):Authenticator,AuthenticatedContent,AccountMenuBar,onAuthChange,broadcastAuthChange— framework-agnostic, return DOM nodes. See the@aws-blocks/auth-commonREADME. - SSR (
@aws-blocks/blocks/server):withAuthforwards browser cookies to API calls during server rendering. See the@aws-blocks/coreREADME. - Wire protocol & debugging: the client is JSON-RPC 2.0 under
/aws-blocks/api— you should never call it directly. Forcurl-level troubleshooting, see TROUBLESHOOTING.md.