{"id":20284172,"url":"https://github.com/clebert/aws-simple","last_synced_at":"2025-04-11T08:24:32.835Z","repository":{"id":40245613,"uuid":"210685708","full_name":"clebert/aws-simple","owner":"clebert","description":"Production-ready AWS website deployment with minimal configuration.","archived":false,"fork":false,"pushed_at":"2024-02-29T16:25:35.000Z","size":4332,"stargazers_count":14,"open_issues_count":5,"forks_count":9,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-09T10:04:11.376Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clebert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-24T19:51:30.000Z","updated_at":"2025-04-09T09:51:55.000Z","dependencies_parsed_at":"2023-02-18T15:45:34.772Z","dependency_job_id":"0b2da0aa-d30a-47e5-bfd1-cd996798e0c7","html_url":"https://github.com/clebert/aws-simple","commit_stats":{"total_commits":627,"total_committers":12,"mean_commits":52.25,"dds":0.1722488038277512,"last_synced_commit":"225e6b04c11374e7ce297a57d68db2379faf0783"},"previous_names":[],"tags_count":123,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clebert%2Faws-simple","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clebert%2Faws-simple/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clebert%2Faws-simple/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clebert%2Faws-simple/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clebert","download_url":"https://codeload.github.com/clebert/aws-simple/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248361084,"owners_count":21090814,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T14:18:36.274Z","updated_at":"2025-04-11T08:24:32.806Z","avatar_url":"https://github.com/clebert.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aws-simple\n\n\u003e Production-ready AWS website deployment with minimal configuration.\n\n## Installation\n\n```\nnpm install aws-simple aws-cdk\n```\n\n## Getting started\n\nThe following are the steps to deploy a website using `aws-simple` and the AWS CDK.\n\n### 1. Create a config file\n\nCreate a config file named `aws-simple.config.mjs`, which exports a function that describes a\nwebsite stack:\n\n```js\n// @ts-check\n\n/** @type {import('aws-simple').ConfigFileDefaultExport} */\nexport default (port) =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n});\n```\n\nThe exported function optionally gets a DEV server `port` argument when called in the context of the\n`aws-simple start [options]` CLI command.\n\n### 2. Create a public hosted zone on AWS Route 53\n\nCreate a **public** hosted zone on AWS Route 53 to make a website available under a particular\ndomain. The required certificate is created automatically by `aws-simple` during deployment.\n\n### 3. Create an AWS IAM user\n\nCreate an AWS IAM user with programmatic access and an [AWS IAM policy](#aws-iam-policy-example)\nwith sufficient permissions.\n\n### 4. Set the credentials\n\nSet the credentials of the AWS IAM user using the two environment variables, `AWS_ACCESS_KEY_ID` and\n`AWS_SECRET_ACCESS_KEY`. Alternatively, the credentials are retrieved using the AWS profile.\n\n### 5. Set the AWS region\n\nSet the AWS region using either the environment variable `AWS_REGION` or `AWS_DEFAULT_REGION`\nevaluated in the specified order. Alternatively, the region is retrieved using the AWS profile.\n\n### 6. Bootstrap the AWS environment\n\n```\nnpx cdk bootstrap --app 'npx aws-simple synthesize'\n```\n\n### 7. Deploy a website to AWS\n\n```\nnpx cdk deploy --app 'npx aws-simple synthesize' \u0026\u0026 npx aws-simple upload\n```\n\n### 8. Optional: Start a local DEV server\n\n```\nnpx aws-simple start\n```\n\n## CLI usage\n\n```\nUsage: aws-simple \u003ccommand\u003e [options]\n\nCommands:\n  aws-simple synthesize [options]   Synthesize the configured stack using the CDK.  [aliases: synth]\n  aws-simple upload [options]       Upload all referenced files to the S3 bucket of the configured stack.\n  aws-simple list [options]         List all deployed stacks filtered by the specified hosted zone name.\n  aws-simple tag [options]          Update the tags of the specified stack.\n  aws-simple delete [options]       Delete the specified stack.\n  aws-simple purge [options]        Delete all expired stacks filtered by the specified hosted zone name.\n  aws-simple flush-cache [options]  Flush the REST API cache of the specified stack.\n  aws-simple redeploy [options]     Redeploy the REST API of the specified stack.\n  aws-simple cleanup [options]      Deletes unused account-wide resources created by aws-simple.\n  aws-simple start [options]        Start a local DEV server.\n\nOptions:\n      --version  Show version number  [boolean]\n  -h, --help     Show help  [boolean]\n```\n\n## Configuration\n\n### Alias record name\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  aliasRecordName: `stage`, // \u003c==\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n});\n```\n\nAn optional alias record name allows multiple website variants to be deployed and operated\nsimultaneously. Example: `stage.example.com`, `test.example.com`\n\nExcept for the specified hosted zone, the website variants do not share any infrastructure. For the\nmanagement of multiple website variants, there are the following two CLI commands:\n\n- `aws-simple list [options]`\n- `aws-simple purge [options]`\n\n### S3 file routes\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `file`, // \u003c==\n      publicPath: `/`,\n      path: `dist/index.html`,\n\n      // optional\n      responseHeaders: { 'cache-control': `max-age=157680000` },\n    },\n  ],\n});\n```\n\n### Lambda function routes\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `function`, // \u003c==\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`, // must be unique per stack and as short as possible\n\n      // optional\n      memorySize: 1769, // default: `128` MB\n      timeoutInSeconds: 3, // default: `28` seconds (this is the maximum timeout)\n      environment: { FOO: `bar` },\n      requestParameters: { foo: {}, bar: { cacheKey: true, required: true } },\n    },\n  ],\n});\n```\n\n```cjs\n// dist/hello.js\nexports.handler = async () =\u003e ({\n  statusCode: 200,\n  body: JSON.stringify({ hello: `world` }),\n});\n```\n\n### Wildcard file/function routes\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `file`,\n      publicPath: `/*`, // \u003c== matches '/', '/foo', '/foo/bar'\n      path: `dist/index.html`,\n    },\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello/*`, // \u003c== matches '/hello', '/hello/world'\n      path: `dist/hello.js`,\n      functionName: `hello`,\n    },\n  ],\n});\n```\n\n### S3 folder routes\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `folder`, // \u003c==\n      publicPath: `/*`, // matches '/foo' and '/foo/bar' but not '/'\n      path: `dist`,\n\n      // optional\n      responseHeaders: { 'cache-control': `max-age=157680000` },\n    },\n  ],\n});\n```\n\n### Caching\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  cachingEnabled: true, // \u003c==\n  routes: [\n    {\n      type: `file`,\n      publicPath: `/`,\n      path: `dist/index.html`,\n      cacheTtlInSeconds: 3600, // default: `300` seconds (if caching is enabled)\n    },\n    {\n      type: `folder`,\n      publicPath: `/*`,\n      path: `dist`,\n      cacheTtlInSeconds: 3600, // default: `300` seconds (if caching is enabled)\n    },\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`,\n      cacheTtlInSeconds: 3600, // default: `300` seconds (if caching is enabled)\n    },\n  ],\n});\n```\n\n### Authentication\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  authentication: {\n    username: `johndoe`, // \u003c==\n    password: `123456`, // \u003c==\n\n    // optional\n    cacheTtlInSeconds: 3600, // default: `300` seconds (if caching is enabled)\n    realm: `foo`,\n  },\n  routes: [\n    {\n      type: `file`,\n      publicPath: `/`,\n      path: `dist/index.html`,\n      authenticationEnabled: true, // \u003c==\n    },\n    {\n      type: `folder`,\n      publicPath: `/*`,\n      path: `dist`,\n      authenticationEnabled: true, // \u003c==\n    },\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`,\n      authenticationEnabled: true, // \u003c==\n    },\n  ],\n});\n```\n\n### CORS\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `file`,\n      publicPath: `/`,\n      path: `dist/index.html`,\n      corsEnabled: true, // \u003c==\n    },\n    {\n      type: `folder`,\n      publicPath: `/*`,\n      path: `dist`,\n      corsEnabled: true, // \u003c==\n    },\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`,\n      corsEnabled: true, // \u003c==\n    },\n  ],\n});\n```\n\n```cjs\n// dist/hello.js\nexports.handler = async () =\u003e ({\n  statusCode: 200,\n  body: JSON.stringify({ hello: `world` }),\n  headers: {\n    'access-control-allow-origin': `*`, // \u003c==\n  },\n});\n```\n\n### Monitoring\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  monitoring: {\n    accessLoggingEnabled: true, // \u003c==\n    lambdaInsightsEnabled: true, // \u003c==\n    loggingEnabled: true, // \u003c==\n    metricsEnabled: true, // \u003c==\n    tracingEnabled: true, // \u003c==\n  },\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n});\n```\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  monitoring: true, // \u003c== shorthand form\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n});\n```\n\n### Throttling\n\n```js\n// @ts-check\n\n/** @type {import('aws-simple').Throttling} */\nconst throttling = {\n  rateLimit: 100, // default: `10000` requests per second\n  burstLimit: 50, // default: `5000` requests\n};\n\n/** @type {import('aws-simple').ConfigFileDefaultExport} */\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `file`,\n      publicPath: `/`,\n      path: `dist/index.html`,\n      throttling, // \u003c==\n    },\n    {\n      type: `folder`,\n      publicPath: `/*`,\n      path: `dist`,\n      throttling, // \u003c==\n    },\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`,\n      throttling, // \u003c==\n    },\n  ],\n});\n```\n\n### Tagging\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  tags: { foo: `bar`, baz: `qux` }, // \u003c==\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n});\n```\n\n### Termination protection\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  terminationProtectionEnabled: true, // \u003c==\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n});\n```\n\n### Source maps\n\n#### Enabling source maps for a Lambda function on AWS\n\n```js\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`,\n      environment: { NODE_OPTIONS: `--enable-source-maps` }, // \u003c==\n    },\n  ],\n});\n```\n\n#### Enabling source maps for a local DEV Server\n\n```\nnode --enable-source-maps $(npm bin)/aws-simple start\n```\n\n### `onSynthesize` hooks\n\nTo implement advanced features, `onSynthesize` hooks can be used. Below are two examples.\n\n#### Configuring a firewall\n\n```js\nimport { aws_wafv2 } from 'aws-cdk-lib';\n\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n\n  onSynthesize: ({ stack, restApi }) =\u003e {\n    const myWebAclArn = `...`;\n\n    new aws_wafv2.CfnWebACLAssociation(stack, `WebACLAssociation`, {\n      resourceArn: restApi.deploymentStage.stageArn,\n      webAclArn: myWebAclArn,\n    });\n  },\n});\n```\n\n#### Allowing a Lambda function read-only access to S3 buckets\n\n```js\nimport { aws_iam } from 'aws-cdk-lib';\n\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`,\n\n      onSynthesize: ({ stack, restApi, lambdaFunction }) =\u003e {\n        lambdaFunction.role.addManagedPolicy(\n          aws_iam.ManagedPolicy.fromAwsManagedPolicyName(`AmazonS3ReadOnlyAccess`),\n        );\n      },\n    },\n  ],\n});\n```\n\n#### Allowing a Lambda function to access a secret in the AWS Secret Manager\n\n```js\nimport { aws_iam } from 'aws-cdk-lib';\n\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [\n    {\n      type: `function`,\n      httpMethod: `GET`,\n      publicPath: `/hello`,\n      path: `dist/hello.js`,\n      functionName: `hello`,\n\n      onSynthesize: ({ stack, restApi, lambdaFunction }) =\u003e {\n        const mySecretId = `...`;\n\n        const secretsManagerPolicyStatement = new aws_iam.PolicyStatement({\n          effect: aws_iam.Effect.ALLOW,\n          actions: [`secretsmanager:GetSecretValue`],\n          resources: [\n            `arn:aws:secretsmanager:${stack.region}:${stack.account}:secret:${mySecretId}`,\n          ],\n        });\n\n        lambdaFunction.addToRolePolicy(secretsManagerPolicyStatement);\n      },\n    },\n  ],\n});\n```\n\n### `onStart` hook\n\nThe `onStart` hook can be used to customize the DEV server's\n[Express app](https://expressjs.com/en/5x/api.html#app), e.g. to configure a proxy middleware:\n\n```js\nimport { createProxyMiddleware } from 'http-proxy-middleware';\n\nexport default () =\u003e ({\n  hostedZoneName: `example.com`,\n  routes: [{ type: `file`, publicPath: `/`, path: `dist/index.html` }],\n  onStart: (app) =\u003e {\n    app.use(\n      `/some-external-api`,\n      createProxyMiddleware({\n        target: `http://www.example.org`,\n        changeOrigin: true,\n      }),\n    );\n  },\n});\n```\n\nNote: The `onStart` hook is called before the routes are registered.\n\n## AWS IAM policy example\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"Bootstrap0\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"cloudformation:*\",\n      \"Resource\": \"arn:aws:cloudformation:*:*:stack/CDKToolkit/*\"\n    },\n    {\n      \"Sid\": \"Bootstrap1\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"iam:*\",\n      \"Resource\": \"arn:aws:iam::*:role/cdk-*\"\n    },\n    {\n      \"Sid\": \"Bootstrap2\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"ssm:*\",\n      \"Resource\": \"arn:aws:ssm:*:*:parameter/cdk-bootstrap/*\"\n    },\n    {\n      \"Sid\": \"Bootstrap3\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"ecr:*\",\n      \"Resource\": \"arn:aws:ecr:*:*:repository/cdk-*\"\n    },\n    {\n      \"Sid\": \"Bootstrap4\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"s3:*\",\n      \"Resource\": \"arn:aws:s3:::cdk-*\"\n    },\n    {\n      \"Sid\": \"AwsSimple0\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"route53:ListHostedZonesByName\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"AwsSimple1\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"cloudformation:*\",\n      \"Resource\": \"arn:aws:cloudformation:*:*:stack/aws-simple-*\"\n    },\n    {\n      \"Sid\": \"AwsSimple2\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"s3:*\",\n      \"Resource\": \"arn:aws:s3:::aws-simple-*\"\n    },\n    {\n      \"Sid\": \"AwsSimple3\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"apigateway:POST\",\n      \"Resource\": \"arn:aws:apigateway:*::/restapis/*/deployments\"\n    },\n    {\n      \"Sid\": \"AwsSimple4\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"apigateway:PATCH\",\n      \"Resource\": \"arn:aws:apigateway:*::/restapis/*/stages/prod\"\n    },\n    {\n      \"Sid\": \"AwsSimple5\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"cloudformation:DescribeStacks\",\n      \"Resource\": \"*\"\n    },\n    {\n      \"Sid\": \"AwsSimple6\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"apigateway:DELETE\",\n      \"Resource\": \"arn:aws:apigateway:*::/restapis/*/stages/prod/cache/data\"\n    },\n    {\n      \"Sid\": \"AwsSimple7\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"apigateway:GET\",\n      \"Resource\": \"arn:aws:apigateway:*::/account\"\n    },\n    {\n      \"Sid\": \"AwsSimple8\",\n      \"Effect\": \"Allow\",\n      \"Action\": \"iam:ListRoles\",\n      \"Resource\": \"arn:aws:iam::*:role/\"\n    },\n    {\n      \"Sid\": \"AwsSimple9\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\"iam:ListAttachedRolePolicies\", \"iam:DetachRolePolicy\", \"iam:DeleteRole\"],\n      \"Resource\": \"arn:aws:iam::*:role/aws-simple-*\"\n    }\n  ]\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclebert%2Faws-simple","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclebert%2Faws-simple","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclebert%2Faws-simple/lists"}