> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/alchemy-run/alchemy/llms.txt
> Use this file to discover all available pages before exploring further.

# AWS Full-Stack

> Build a complete full-stack application on AWS with Lambda, DynamoDB, and API Gateway

# AWS Full-Stack Example

This example demonstrates building a complete full-stack serverless application on AWS. The example uses the same infrastructure as the AWS Lambda example but can be extended with additional frontend resources.

## Features

* **Lambda Function**: Serverless API endpoints
* **DynamoDB Table**: NoSQL database for data persistence
* **SQS Queue**: Async message processing
* **IAM Roles**: Security and permissions
* **Function URLs**: Direct HTTP access
* **CORS**: Cross-origin resource sharing

## Architecture

The full-stack application consists of:

1. **Backend**: AWS Lambda functions with TypeScript
2. **Database**: DynamoDB for data storage
3. **Queue**: SQS for background jobs
4. **API**: Function URLs with CORS for frontend access

## Project Setup

<Steps>
  ### Install Dependencies

  ```bash theme={null}
  npm install alchemy
  npm install @aws-sdk/client-dynamodb @aws-sdk/client-sqs
  npm install -D @types/aws-lambda
  ```

  ### Create alchemy.run.ts

  See the [AWS Lambda example](/examples/aws-lambda) for the complete infrastructure setup:

  ```ts theme={null}
  import alchemy from "alchemy";
  import { Function, Queue, Role, Table } from "alchemy/aws";
  import { Bundle } from "alchemy/esbuild";
  import path from "node:path";
  import { fileURLToPath } from "node:url";

  const app = await alchemy("aws-app");

  const __dirname = path.dirname(fileURLToPath(import.meta.url));

  // Create infrastructure resources in parallel
  const [queue, table, role] = await Promise.all([
    Queue("queue", {
      queueName: `${app.name}-${app.stage}-queue`,
      visibilityTimeout: 30,
      messageRetentionPeriod: 345600,
    }),
    Table("table", {
      tableName: `${app.name}-${app.stage}-table`,
      partitionKey: { name: "id", type: "S" },
    }),
    Role("role", {
      roleName: `${app.name}-${app.stage}-lambda-role`,
      assumeRolePolicy: {
        Version: "2012-10-17",
        Statement: [
          {
            Effect: "Allow",
            Principal: { Service: "lambda.amazonaws.com" },
            Action: "sts:AssumeRole",
          },
        ],
      },
    }),
  ]);

  // Bundle Lambda code
  const bundle = await Bundle("api-bundle", {
    entryPoint: path.join(__dirname, "src", "index.ts"),
    outdir: ".out",
    format: "esm",
    platform: "node",
    target: "node20",
    minify: true,
    external: ["@aws-sdk/*"],
  });

  // Create Lambda function with Function URL
  const api = await Function("api", {
    functionName: `${app.name}-${app.stage}-api`,
    bundle,
    roleArn: role.arn,
    handler: "index.handler",
    environment: {
      TABLE_NAME: table.tableName,
      QUEUE_URL: queue.url,
    },
    url: {
      authType: "NONE",
      cors: {
        allowOrigins: ["*"],
        allowMethods: ["GET", "POST", "PUT", "DELETE"],
        allowHeaders: ["content-type"],
      },
    },
  });

  console.log(`API URL: ${api.url}`);

  await app.finalize();
  ```

  ### Create API Handler

  Create `src/index.ts` with full CRUD operations:

  ```ts theme={null}
  import {
    DynamoDBClient,
    PutItemCommand,
    GetItemCommand,
    ScanCommand,
    DeleteItemCommand,
  } from "@aws-sdk/client-dynamodb";
  import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
  import type { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";

  const dynamodb = new DynamoDBClient({});
  const sqs = new SQSClient({});

  const TABLE_NAME = process.env.TABLE_NAME!;
  const QUEUE_URL = process.env.QUEUE_URL!;

  export async function handler(
    event: APIGatewayProxyEvent
  ): Promise<APIGatewayProxyResult> {
    const method = event.requestContext.http.method;
    const path = event.requestContext.http.path;

    try {
      // POST /items - Create new item
      if (method === "POST" && path === "/items") {
        const body = JSON.parse(event.body || "{}");
        const id = crypto.randomUUID();

        await dynamodb.send(
          new PutItemCommand({
            TableName: TABLE_NAME,
            Item: {
              id: { S: id },
              data: { S: JSON.stringify(body) },
              createdAt: { N: Date.now().toString() },
            },
          })
        );

        // Send to queue for async processing
        await sqs.send(
          new SendMessageCommand({
            QueueUrl: QUEUE_URL,
            MessageBody: JSON.stringify({ id, action: "created" }),
          })
        );

        return {
          statusCode: 201,
          body: JSON.stringify({ id, message: "Created" }),
        };
      }

      // GET /items/:id - Get specific item
      if (method === "GET" && path.startsWith("/items/")) {
        const id = path.split("/")[2];

        const result = await dynamodb.send(
          new GetItemCommand({
            TableName: TABLE_NAME,
            Key: { id: { S: id } },
          })
        );

        if (!result.Item) {
          return {
            statusCode: 404,
            body: JSON.stringify({ error: "Not found" }),
          };
        }

        return {
          statusCode: 200,
          body: JSON.stringify(result.Item),
        };
      }

      // GET /items - List all items
      if (method === "GET" && path === "/items") {
        const result = await dynamodb.send(
          new ScanCommand({ TableName: TABLE_NAME })
        );

        return {
          statusCode: 200,
          body: JSON.stringify(result.Items || []),
        };
      }

      // DELETE /items/:id - Delete item
      if (method === "DELETE" && path.startsWith("/items/")) {
        const id = path.split("/")[2];

        await dynamodb.send(
          new DeleteItemCommand({
            TableName: TABLE_NAME,
            Key: { id: { S: id } },
          })
        );

        return {
          statusCode: 200,
          body: JSON.stringify({ message: "Deleted" }),
        };
      }

      return {
        statusCode: 404,
        body: JSON.stringify({ error: "Route not found" }),
      };
    } catch (error) {
      console.error(error);
      return {
        statusCode: 500,
        body: JSON.stringify({ error: "Internal server error" }),
      };
    }
  }
  ```

  ### Deploy

  Deploy the full-stack application:

  ```bash theme={null}
  npm exec tsx alchemy.run.ts
  ```
</Steps>

## API Endpoints

Once deployed, your API supports:

* `POST /items` - Create a new item
* `GET /items` - List all items
* `GET /items/:id` - Get a specific item
* `DELETE /items/:id` - Delete an item

## Frontend Integration

You can connect any frontend framework:

```ts theme={null}
// Example fetch call
const response = await fetch('https://your-function-url.lambda-url.us-east-1.on.aws/items', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Example Item' }),
});

const data = await response.json();
console.log(data.id); // UUID of created item
```

## Key Features Explained

### Parallel Resource Creation

Create multiple resources concurrently for faster deployment:

```ts theme={null}
const [queue, table, role] = await Promise.all([...]);
```

### Environment Variables

Pass resource references to Lambda:

```ts theme={null}
environment: {
  TABLE_NAME: table.tableName,
  QUEUE_URL: queue.url,
}
```

### CORS Configuration

Enable frontend access:

```ts theme={null}
cors: {
  allowOrigins: ["*"],
  allowMethods: ["GET", "POST", "PUT", "DELETE"],
  allowHeaders: ["content-type"],
}
```

## Source Code

View the complete source code: [examples/aws-app](https://github.com/sam-goodwin/alchemy/tree/main/examples/aws-app)
