Skip to content
GitHubXDiscord

AWS Cloud Control API

The AWS Cloud Control API integration in Alchemy provides a unified interface for managing AWS resources using the AWS Cloud Control API. This integration allows you to create, read, update, and delete any AWS resource that is supported by CloudFormation, using a consistent and type-safe interface.

First, ensure you have the required peer dependency installed:

Terminal window
npm install aws4fetch

Then, import the AWS Cloud Control module:

import AWS from "alchemy/aws/control";

The AWS Cloud Control integration provides a proxy-based interface that mirrors the AWS service and resource structure. For example, to create an S3 bucket:

import AWS from "alchemy/aws/control";
const bucket = await AWS.S3.Bucket("my-bucket", {
BucketName: "my-unique-bucket-name",
VersioningConfiguration: {
Status: "Enabled",
},
});
console.log(bucket.BucketName); // "my-unique-bucket-name"

All AWS resources that are supported by CloudFormation can be accessed through the AWS namespace. The resource types follow the CloudFormation naming convention:

  • AWS.S3.Bucket for AWS::S3::Bucket
  • AWS.DynamoDB.Table for AWS::DynamoDB::Table
  • AWS.Lambda.Function for AWS::Lambda::Function
  • And so on…

To create a resource, call the resource type function with an ID and configuration:

const table = await AWS.DynamoDB.Table("users-table", {
TableName: "users",
AttributeDefinitions: [
{
AttributeName: "id",
AttributeType: "S",
},
],
KeySchema: [
{
AttributeName: "id",
KeyType: "HASH",
},
],
ProvisionedThroughput: {
ReadCapacityUnits: 5,
WriteCapacityUnits: 5,
},
});

To update a resource, call the resource type function with the same ID and new configuration:

const updatedBucket = await AWS.S3.Bucket("my-bucket", {
BucketName: "my-unique-bucket-name",
VersioningConfiguration: {
Status: "Suspended", // Change versioning status
},
});

Resources are automatically deleted when they go out of scope or when the destroy() function is called:

import { destroy } from "alchemy/destroy";
// Delete all resources in the scope
await destroy(scope);

You can reference attributes from one resource in another:

// Create an IAM role for Lambda
const role = await AWS.IAM.Role("lambda-role", {
RoleName: "my-lambda-role",
AssumeRolePolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: {
Service: "lambda.amazonaws.com",
},
Action: "sts:AssumeRole",
},
],
},
});
// Create a Lambda function using the role
const func = await AWS.Lambda.Function("my-function", {
FunctionName: "my-function",
Role: role.Arn, // Reference the role's ARN
Code: {
ZipFile: "exports.handler = async () => ({ statusCode: 200 });",
},
Handler: "index.handler",
Runtime: "nodejs20.x",
});

The Cloud Control API client includes comprehensive error handling:

try {
const bucket = await AWS.S3.Bucket("my-bucket", {
BucketName: "invalid/name", // Invalid bucket name
});
} catch (error) {
if (error.code === "ValidationException") {
console.error("Invalid resource configuration:", error.message);
} else if (error.retryable) {
console.error("Transient error, retry the operation:", error.message);
} else {
console.error("Unexpected error:", error);
}
}

You can configure the Cloud Control API client behavior:

import { createCloudControlClient } from "alchemy/aws/control/client";
const client = createCloudControlClient({
region: "us-west-2", // AWS region
maxPollingAttempts: 60, // Maximum attempts for polling operations
initialPollingDelay: 2000, // Initial delay between polling attempts (ms)
maxPollingDelay: 20000, // Maximum delay between polling attempts (ms)
operationTimeout: 600000, // Overall operation timeout (ms)
maxRetries: 5, // Maximum retries for retryable errors
});
  1. Resource Updates

  2. Operation Timing

    • Resource creation and updates are asynchronous
    • Operations may take several minutes to complete
    • The client includes built-in polling with exponential backoff
  3. API Limits

    • Cloud Control API has service quotas and throttling limits
    • The client includes automatic retries for throttling errors
    • Consider implementing your own rate limiting for large-scale operations
import AWS from "alchemy/aws/control";
import { alchemy } from "alchemy";
const app = alchemy.app(import.meta);
// Create a bucket for static website hosting
const bucket = await AWS.S3.Bucket("website", {
BucketName: "my-static-website",
WebsiteConfiguration: {
IndexDocument: "index.html",
ErrorDocument: "error.html",
},
PublicAccessBlockConfiguration: {
BlockPublicAcls: false,
BlockPublicPolicy: false,
IgnorePublicAcls: false,
RestrictPublicBuckets: false,
},
});
// Add bucket policy for public read access
const policy = await AWS.S3.BucketPolicy("website-policy", {
Bucket: bucket.BucketName,
PolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: "*",
Action: "s3:GetObject",
Resource: `${bucket.Arn}/*`,
},
],
},
});
console.log(
"Website URL:",
`http://${bucket.BucketName}.s3-website-${app.region}.amazonaws.com`
);
import AWS from "alchemy/aws/control";
const table = await AWS.DynamoDB.Table("users", {
TableName: "users",
AttributeDefinitions: [
{ AttributeName: "id", AttributeType: "S" },
{ AttributeName: "email", AttributeType: "S" },
{ AttributeName: "createdAt", AttributeType: "N" },
],
KeySchema: [{ AttributeName: "id", KeyType: "HASH" }],
GlobalSecondaryIndexes: [
{
IndexName: "email-index",
KeySchema: [{ AttributeName: "email", KeyType: "HASH" }],
Projection: { ProjectionType: "ALL" },
ProvisionedThroughput: {
ReadCapacityUnits: 5,
WriteCapacityUnits: 5,
},
},
{
IndexName: "createdAt-index",
KeySchema: [{ AttributeName: "createdAt", KeyType: "HASH" }],
Projection: { ProjectionType: "KEYS_ONLY" },
ProvisionedThroughput: {
ReadCapacityUnits: 5,
WriteCapacityUnits: 5,
},
},
],
ProvisionedThroughput: {
ReadCapacityUnits: 5,
WriteCapacityUnits: 5,
},
StreamSpecification: {
StreamEnabled: true,
StreamViewType: "NEW_AND_OLD_IMAGES",
},
});
console.log("Table Stream ARN:", table.StreamArn);
import AWS from "alchemy/aws/control";
// Create IAM role for Lambda
const role = await AWS.IAM.Role("lambda-role", {
RoleName: "my-lambda-role",
AssumeRolePolicyDocument: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: {
Service: "lambda.amazonaws.com",
},
Action: "sts:AssumeRole",
},
],
},
ManagedPolicyArns: [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
],
});
// Create CloudWatch log group
const logGroup = await AWS.Logs.LogGroup("lambda-logs", {
LogGroupName: "/aws/lambda/my-function",
RetentionInDays: 14,
});
// Create Lambda function
const func = await AWS.Lambda.Function("my-function", {
FunctionName: "my-function",
Role: role.Arn,
Code: {
ZipFile: `
exports.handler = async (event) => {
console.log("Event:", JSON.stringify(event));
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello from Lambda!" }),
};
};
`,
},
Handler: "index.handler",
Runtime: "nodejs20.x",
Environment: {
Variables: {
LOG_LEVEL: "INFO",
},
},
TracingConfig: {
Mode: "Active", // Enable X-Ray tracing
},
});
console.log("Function ARN:", func.FunctionArn);
  1. Resource Creation Failures

    try {
    const bucket = await AWS.S3.Bucket("my-bucket", config);
    } catch (error) {
    if (error.code === "ResourceConflict") {
    console.error("Resource already exists");
    } else if (error.code === "ValidationException") {
    console.error("Invalid configuration:", error.message);
    } else if (error.code === "AccessDeniedException") {
    console.error("Check IAM permissions");
    }
    }
  2. Operation Timeouts

    const client = createCloudControlClient({
    maxPollingAttempts: 60, // Increase for slow operations
    operationTimeout: 900000, // 15 minutes
    });
  3. Rate Limiting

    // Implement your own rate limiting
    const delay = (ms: number) =>
    new Promise((resolve) => setTimeout(resolve, ms));
    for (const config of resourceConfigs) {
    await delay(1000); // Wait 1 second between operations
    await AWS.S3.Bucket(`bucket-${config.id}`, config);
    }
  1. Always use proper error handling
  2. Implement retries with exponential backoff
  3. Use resource IDs that are unique within your scope
  4. Clean up resources after tests
  5. Monitor operation progress and timeouts
  6. Use TypeScript for better type safety
  7. Keep resource configurations modular and reusable