Blocks: keeping your Anvil config readable
Blocks group your resources by domain so your Anvil config stays readable as your App grows. What they are, how they compare to CDK Stacks, and what they aren't.
Today we are talking about blocks. Not the toys kids play with, and not what happens when you send a message and someone doesn't like it.
The problem with a single entry point for deploying your resources is that, as your App grows, it gets big and ugly and hard to read. Blocks were created to help preserve the config file's precious space. Nothing is worse than opening a file and seeing 3,000 lines of code, knowing only where it starts and ends, with no knowledge of what's in the middle.
If you have multiple resources shared across parts of your infrastructure or system, you can deploy them directly in an Anvil config file. Blocks will basically condense those resources into a class, and they will have the exact same functionality as if they were deployed one by one.
I am sure with the emergence of AI, everyone has seen the amazing "dump everything right here" file that doesn't need to be readable or maintainable at all. Just use Anvil Blocks. It's easier; everyone will love you.
All jokes aside, they are completely optional and suit larger projects where you have a clear separation of domains within your application. Think of the classic example of an e-commerce store with inventory, payments, and shipping domains. Even at small scale, some of these domains will have many resources.
Instead of having a config file with hundreds or thousands of resources per domain at large-scale operations, you can use Anvil Blocks to make it readable and easier to search. At that scale, you would probably use independent deployments for those services, but my point stands.
Here's the "everything in one file" version:
// anvil.config.ts
import { App } from '@anvil-cloud/sdk';
import * as anvil from '@anvil-cloud/sdk';
export default new App({
run(ctx) {
const inventoryTable = new anvil.aws.DynamoDB('inventory-table', {
hashKey: { name: 'sku', type: 'S' },
});
const reserveStock = new anvil.aws.Lambda('reserve-stock', {
runtime: 'nodejs24.x',
handler: 'handlers/inventory/reserveStock.handler',
environment: { TABLE_NAME: inventoryTable.tableName },
});
inventoryTable.grantReadWrite(reserveStock);
// ...
// a few thousand lines later
// ...
const shippingLabels = new anvil.aws.Bucket('shipping-labels');
const createLabel = new anvil.aws.Lambda('create-label', {
runtime: 'nodejs24.x',
handler: 'handlers/shipping/createLabel.handler',
environment: { LABELS_BUCKET: shippingLabels.bucketName },
});
shippingLabels.grantWrite(createLabel);
},
});
And the same App with one Block per domain:
// anvil.config.ts
import { App } from '@anvil-cloud/sdk';
import { InventoryBlock } from './blocks/inventory';
import { PaymentsBlock } from './blocks/payments';
import { ShippingBlock } from './blocks/shipping';
export default new App({
run(ctx) {
new InventoryBlock('inventory');
new PaymentsBlock('payments');
new ShippingBlock('shipping');
},
});
Each Block is just a class holding that domain's resources:
// blocks/inventory.ts
import * as anvil from '@anvil-cloud/sdk';
export class InventoryBlock extends anvil.Block {
constructor(name: string) {
super(name);
const table = new anvil.aws.DynamoDB('inventory-table', {
hashKey: { name: 'sku', type: 'S' },
});
const reserveStock = new anvil.aws.Lambda('reserve-stock', {
runtime: 'nodejs24.x',
handler: 'handlers/inventory/reserveStock.handler',
environment: { TABLE_NAME: table.tableName },
});
table.grantReadWrite(reserveStock);
this.output('tableName', table.tableName);
}
}
One of my favourite things about CDK best practices is that they force you to group resources into domains within your App. It does this with stacks.
If you are familiar with CDK Stacks, this will feel familiar, and you will feel right at home with some parts of Blocks. They are very similar, but because of the graph-based deployment engine, they don't create a tightly coupled dependency when you reference a value from another block.
Stacks can be deployed independently, but one drawback of the graph-based engine is that the single state file created for the App makes independent deployments hard. Meaning Blocks cannot be deployed independently; it's all or nothing.
The trade-off is that it's easier to delete resources, especially with a new App where you are still figuring things out and unsure where it's going. That's a huge plus; yes, you may get slightly longer deployments, which is annoying, but you also don't risk corrupting stacks and sitting there waiting for CloudFormation to time out so you can manually remove resources, delete the stack, and redeploy, hoping it works.
What they aren't. They aren't like Terraform modules. Modules let you create resources with predefined settings and share them across teams. Anvil at it's core is doing something similar: all components come with built-in security and functionality, plus grants that let you safely grant access between resources.
As always, thank you for taking the time to read about Anvil.
Remember, it's your infrastructure, not mine.
Damien.
Reference docs for this topic: Blocks.