AWS Blocks concepts
This topic explains the key concepts you need to understand when building applications with AWS Blocks.
Blocks
A Block is an npm package that bundles everything for a single feature: cloud resources, runtime code, and a local implementation. When you create a Block in your code, AWS Blocks sets up the matching AWS resources, wires permissions, and gives you a local implementation for development.
For example, KVStore is a Block for key-value storage. A single instantiation gives you:
-
A DynamoDB table (provisioned automatically during deployment)
-
AWS SDK integration (used at runtime in Lambda)
-
A local store (used during local development)
import { KVStore, Scope } from '@aws-blocks/blocks'; const scope = new Scope('my-app'); const cache = new KVStore(scope, 'cache', {}); // Same API works locally and in production await cache.set('user:123', { name: 'Alice' }); const user = await cache.get('user:123');
Scope
A Scope is a namespace container for Blocks. Every Block must be instantiated within a scope. The scope provides identity and grouping. Each Block’s full identifier is derived from its scope and the ID you provide.
const scope = new Scope('my-app'); const users = new KVStore(scope, 'users', {}); // Full ID: my-app/users const sessions = new KVStore(scope, 'sessions', {}); // Full ID: my-app/sessions
Scopes ensure that resource names are unique and predictable across your application.
Warning
Renaming a Block ID (the second argument to the constructor) causes the corresponding AWS resource to be deleted and recreated on the next deployment. This results in permanent data loss for stateful Blocks such as KVStore, DistributedTable, Database, and FileBucket. Always treat Block IDs as immutable once deployed.
The IFC layer
The IFC (Infrastructure from Code) layer is your backend entry point, the aws-blocks/index.ts file. This is where you instantiate Blocks and define your API. AWS Blocks derives your infrastructure directly from this code. You don’t need separate infrastructure-as-code files.
// aws-blocks/index.ts - the IFC layer import { ApiNamespace, Scope, KVStore, AuthBasic } from '@aws-blocks/blocks'; const scope = new Scope('my-app'); // Infrastructure is derived from these instantiations const auth = new AuthBasic(scope, 'auth'); const todos = new KVStore(scope, 'todos', {}); // API methods are callable from the frontend export const api = new ApiNamespace(scope, 'api', (context) => ({ async createTodo(title: string) { const user = await auth.getCurrentUser(context); await todos.set(`${user.userId}:${title}`, { title, done: false }); }, })); export { auth };
The IFC layer serves three purposes simultaneously:
-
Local development: Blocks resolve to local implementations
-
Deployment: Blocks resolve to CDK constructs that define your infrastructure
-
Production runtime: Blocks resolve to AWS SDK integrations running in Lambda
Conditional exports
AWS Blocks uses Node.js conditional exports to route the same import statement to different implementations depending on the execution context. This is the mechanism that makes a single codebase work across local development, deployment, and production.
When you write import { KVStore } from '@aws-blocks/blocks', the resolved file depends on context:
| Context | Resolved implementation | What happens |
|---|---|---|
|
Local development |
In-memory and filesystem |
Your app runs entirely on localhost |
|
CDK synthesis |
CDK construct (DynamoDB table) |
Infrastructure is defined for CloudFormation |
|
Lambda runtime |
AWS SDK (DynamoDB client) |
Real AWS service calls in production |
|
TypeScript/IDE |
Type definitions |
Full IntelliSense and type checking |
You never need to configure conditional exports manually. AWS Blocks sets up the build system to select the correct implementation for each context automatically.
ApiNamespace
An ApiNamespace defines type-safe backend methods that your frontend can call directly. It’s the bridge between your backend logic and your client code.
// Backend: define the API export const api = new ApiNamespace(scope, 'api', (context) => ({ async greet(name: string) { return { message: `Hello, ${name}!` }; }, }));
// Frontend: call the API directly import { api } from '../aws-blocks/index.js'; const result = await api.greet('World'); // result.message === 'Hello, World!' // TypeScript knows the return type
There is no code generation, no API client initialization, and no URL configuration. The frontend import is type-safe. If you change the backend method signature, TypeScript reports errors in the frontend immediately.
Locally, ApiNamespace routes calls through a local HTTP server. In production, calls go through API Gateway to Lambda.
BlocksContext
The BlocksContext is the request/response object provided to ApiNamespace handlers. Blocks that need HTTP-level access (such as authentication blocks reading cookies or setting headers) accept BlocksContext as a parameter.
export const api = new ApiNamespace(scope, 'api', (context) => ({ async protectedAction() { // Auth blocks use context to read headers/cookies const user = await auth.getCurrentUser(context); return { userId: user.userId }; }, }));
You don’t construct BlocksContext yourself. It’s provided automatically by the framework for each incoming request.
The CDK layer
The CDK layer is an optional file (aws-blocks/index.cdk.ts) that gives you direct access to CDK constructs. Use it when you need to:
-
Add AWS resources that don’t have a Block (such as SQS queues or SNS topics)
-
Configure custom domains or other environment-specific settings
-
Integrate AWS Blocks into an existing CDK application
// aws-blocks/index.cdk.ts import * as cdk from 'aws-cdk-lib'; import { BlocksStack } from '@aws-blocks/blocks/cdk'; const app = new cdk.App(); const stack = await BlocksStack.create(app, 'my-stack', { backendHandlerPath: './index.handler.ts', backendCDKPath: './index.ts', }); // Add any CDK construct alongside your Blocks const queue = new sqs.Queue(stack, 'my-queue'); queue.grantSendMessages(stack.handler); stack.handler.addEnvironment('QUEUE_URL', queue.queueUrl);
The CDK layer is optional. If you don’t create one, AWS Blocks generates a default CDK application from your IFC layer automatically.
Local development
When you run npm run dev, AWS Blocks starts your entire application locally:
-
Blocks use local implementations (in-memory stores, local JWT tokens, embedded databases)
-
The application runs on
http://localhost:3000with hot reload -
No AWS account, no internet connection, and no cloud costs required
Local data persists in a .bb-data/ directory at your project root. Each Block gets its own subdirectory.
Sandbox deployments
A sandbox is a fast, ephemeral deployment to AWS for testing against real services. When you run npm run sandbox:
-
AWS Blocks deploys your backend to Lambda with hot-swapping (seconds, not minutes)
-
Blocks resolve to real AWS services (DynamoDB, API Gateway, etc.)
-
Each developer gets an isolated sandbox identified by a unique ID
-
Remove it with
npm run sandbox:destroy
Sandboxes are useful when you need to test behavior that differs between local implementations and real services, such as DynamoDB query performance or IAM permission boundaries.
Terminology reference
| Term | Definition |
|---|---|
|
Block |
A self-contained npm package that bundles infrastructure, runtime, and local development code for a single capability. |
|
Scope |
A namespace container that provides identity to Blocks. |
|
IFC layer |
The backend entry point ( |
|
CDK layer |
An optional file ( |
|
ApiNamespace |
A Block that defines type-safe RPC methods callable from the frontend. |
|
BlocksContext |
The request/response context object provided to API handlers. |
|
Conditional exports |
Node.js mechanism that routes imports to different files based on execution context. |
|
Sandbox |
A fast, ephemeral AWS deployment for testing against real services. |
|
Local implementation |
An in-memory or filesystem-based version of a Block that runs without AWS. |