Skip to content

Commit

Permalink
consistency
Browse files Browse the repository at this point in the history
  • Loading branch information
saddam-azad committed Aug 17, 2024
1 parent d07a621 commit 7f739f7
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 119 deletions.
126 changes: 33 additions & 93 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# CDK-SPA
Deploy SSG (static site generators) and client-only SPA (single page applications) on AWS using CDK.
Deploy any client-only SPA (single page application) on AWS using CDK.

- Fast responses from [CloudFront](https://aws.amazon.com/cloudfront/)
- Automatic upload of the build files for CSR and static assets to [S3](https://aws.amazon.com/s3/) with optimized caching rules
- Automatic build and deploy with [CodeBuild](https://aws.amazon.com/codebuild/) and [CodePipeline](https://aws.amazon.com/codepipeline/) from [Github](https://github.com/) repository.
- Publicly available by a custom domain (or subdomain) via [Route53](https://aws.amazon.com/route53/)
- Automatic cleanup of outdated static assets and build files.


## Prerequisites

You need an [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) to create and deploy the required resources for the site on AWS.

This package uses the `npm` package manager and is an ES Module.
This package uses the `npm` package manager and is an ES6+ Module.

## Installation

Install the package and its required dependencies:

```bash
npm i @thunderso/stacks --save-dev
npm i @thunderso/cdk-spa --save-dev
```

If you do not have already, your `package.json` must also contain `aws-cdk-lib` and `tsx`:
Expand All @@ -32,50 +32,35 @@ npm i aws-cdk-lib tsx --save-dev

2. Run the following command to automatically create the required CDK stack entrypoint at `stack/index.ts`. This file defines the config how the app will be deployed via CDK. You should adapt the file to the project's needs, especially the props `env.account` (setup step 1).


```bash
npx cdk-spa-init
```
The executable file can be found at

```bash
node_modules/.bin/cdk-spa-init
```

### Enable Automatic Deployments

1. [Create a Github Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) for your Github account. This token must be kept secure.

<!-- 2. [Create a Parameter Store parameter (SecureString)](https://docs.aws.amazon.com/kms/latest/developerguide/services-parameter-store.html) with the Personal Access Token you created earlier. Note the `ARN` of the parameter. E.g. `arn:aws:ssm:<REGION_NAME>:<ACCOUNT_ID>:parameter/<parameter-name>`. -->

2. [Create a Secrets Manager secret](https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_create-basic-secret.html) with the Personal Access Token you created earlier. Note the `ARN` of the secret. E.g. `arn:aws:secretsmanager:<REGION_NAME>:<ACCOUNT_ID>:secret:<secret-name>`.

3. Input the noted `ARN` to the `githubAccessTokenArn` field in your stack.

### Manage Domain with Route53 (Optional)

1. [Create a hosted zone in Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/AboutHZWorkingWith.html) for the desired domain, if you don't have one yet.
This is required to create DNS records for the domain to make the app publicly available on that domain.
On the hosted zone details you should see the `Hosted zone ID` of the hosted zone.

This is required to create DNS records for the domain to make the app publicly available on that domain. On the hosted zone details you should see the `Hosted zone ID` of the hosted zone.

2. [Request a public global certificate in the AWS Certificate Manager (ACM)](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html) for the desired domain in `us-east-1` *(global)* and validate it, if you don't have one yet.
This is required to provide the app via HTTPS on the public internet. Take note of the displayed `ARN` for the certificate.

This is required to provide the app via HTTPS on the public internet. Take note of the displayed `ARN` for the certificate.

> [!IMPORTANT]
> The certificate must be issued in `us-east-1` *(global)* regardless of the region used for the app itself as it will be attached to the CloudFront distribution which works globally.

### Custom `buildspec.yml` and CloudFront Functions (Optional)

1. [Build specification reference for CodeBuild](https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html)

2. [CloudFront Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CloudFront-functions.html) are used for URL rewriting.

> Check out the resources from [aws-samples/amazon-cloudfront-functions](https://github.com/aws-samples/amazon-cloudfront-functions) for examples.
## Configuration

The `StaticSiteStack` construct can be configured via the following props:
The `SPAStack` construct can be configured via the following props:

<table width="100%">
<thead align=left>
Expand Down Expand Up @@ -173,7 +158,7 @@ The `StaticSiteStack` construct can be configured via the following props:
<th>
<code>string</code>
</th>
<td><strong>Optional</strong>. If you have a custom CodeBuild Buildspec file for your app, provide relative path to the file. E.g. <code>buildspec.yml</code> or <code>./buildspec.yml</code>
<td><strong>Optional</strong>. If you have a custom CodeBuild Buildspec file for your app, provide relative path to the file. E.g. <code>stack/buildspec.yml</code>
</td>
</tr>
<tr>
Expand Down Expand Up @@ -234,31 +219,10 @@ The `StaticSiteStack` construct can be configured via the following props:
<th>
<code>string</code>
</th>
<td><strong>Optional</strong>. If you have a custom CloudFront Functions file for your app, provide relative path to the file. E.g. <code>./index.js</code>
<td><strong>Optional</strong>. If you have a custom CloudFront Functions file for your app, provide relative path to the file. E.g. <code>stack/urlrewrite.js</code>
</td>
</tr>
<tr>
<tr><td colspan=3><small>Addon functions:</small></td></tr>
<tr id="constructor-option-accesslog">
<th>
<code>enableAnalytics</code>
</th>
<th>
<code>boolean</code>
</th>
<td><strong>Optional</strong>. Whether to provision Athena for CloudFront access logs analysis. Defaults to <code>false</code>
</td>
</tr>
<tr>
<th>
<code>enableAssetCleanup</code>
</th>
<th>
<code>boolean</code>
</th>
<td><strong>Optional</strong>. Whether to provision Lambda and EventBridge rule to cleanup stale static assets. Defaults to <code>false</code>
</td>
</tr>
</tbody>
</table>

Expand All @@ -279,18 +243,7 @@ See https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html for details.

### 2. Build and Deploy

By running the following script, the app will be built automatically via `npm build` and the CDK stack will be deployed to AWS.

```bash
npx cdk-spa-deploy
```
The executable file:

```bash
node_modules/.bin/cdk-spa-deploy
```

Alternatively, you can run the following commands separately to customize the deployment process:
By running the following script, the CDK stack will be deployed to AWS.

```bash
npx cdk deploy --require-approval never --all --app="npx tsx stack/index.ts"
Expand All @@ -300,61 +253,44 @@ npx cdk deploy --require-approval never --all --app="npx tsx stack/index.ts"

If you want to destroy the stack and all its resources (including storage, e.g., access logs), run the following script:


```bash
npx cdk-spa-destroy
```
The executable file:
```bash
node_modules/.bin/cdk-spa-destroy
npx cdk destroy --require-approval never --all --app="npx tsx stack/index.ts"
```

## Reference: Created AWS Resources

In the following, you can find an overview of the AWS resources that will be created by this package for reference.

### StaticSiteStack
### SPAStack

This stack is responsible for deploying static sites and dynamic SPA apps to AWS.
The following AWS resources will be created by this stack:

- [CloudFront](https://aws.amazon.com/CloudFront/): A distribution to route incoming requests to the S3 bucket to serve the static assets for the app.

- [S3](https://aws.amazon.com/s3/):
- A bucket to store the client files and static assets of the build with optimized cache settings.
- A bucket to store the CloudFront access logs for analysis via Athena. Only created if `enableAnalytics` is set to `true`.
- [S3](https://aws.amazon.com/s3/): A bucket to store the client files and static assets of the build with optimized cache settings.

- [CodeBuild](https://aws.amazon.com/codebuild/): A build project to automatically build and package the static assets from the source code.

- [CodePipeline](https://aws.amazon.com/codepipeline/): A deployment pipeline to automate the release process, integrating with CodeBuild, S3, and other AWS services.

- [Route53](https://aws.amazon.com/route53/): Two DNS records (`A` for IPv4 and `AAAA` for IPv6) in the configured hosted zone to make the app available on the internet via the configured custom domain.

- [Lambda](https://aws.amazon.com/lambda/):
- A Lambda function that deletes the outdated static assets of the app from S3.

- [EventBridge](https://aws.amazon.com/eventbridge/):
- A scheduled rule to trigger the cleanup Lambda function for deleting the outdated static assets of the app from S3 every tuesday at 03:30 AM GMT.

- [Athena](https://aws.amazon.com/athena/): A database and table to analyze the access logs of the app's CloudFront distribution. Only created if `enableAnalytics` is set to `true`.

- [AppConfig](https://aws.amazon.com/appconfig/): An application and environment configuration for deployment settings management.


## Manually setting up CDK

You can use `StaticSiteStack` as a CDK construct within your CDK code to seamlessly integrate hosting. Here's an example of how to use it:
You can use `SPAStack` as a CDK construct within your CDK code to seamlessly integrate hosting. Here's an example of how to use it:

Create a `stacks` directory in your project root and an empty file `index.ts` and fill in the props accordingly.
Create a `stack` directory in your project root and an empty file `index.ts` and fill in the props accordingly.

> Use different filenames such as `production.ts` and `testing.ts` for environments.
```ts
#!/usr/bin/env node
import { App } from "aws-cdk-lib";
import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa";
import { SPAStack, type SPAProps } from "@thunderso/cdk-spa";

const appStackProps: StaticSiteProps = {
const appStackProps: SPAProps = {
env: {
account: 'your-account-id',
region: 'us-east-1'
Expand All @@ -378,41 +314,45 @@ const appStackProps: StaticSiteProps = {
// Either provide a buildspec.yml file OR leave empty and fill out buildProps
buildSpecFilePath: './buildspec.yml',
buildProps: {
runtime: 20, // nodejs version
runtime: 20, // nodejs versions 16, 18 and 20 supported
installcmd: "npm ci",
buildcmd: "npm run build",
outputDir: "dist"
},

// Custom CloudFront Functions for URL rewrite
edgeFunctionFilePath: 'stack/urlrewrite.js',

// Optional: Domain settings
// - create a hosted zone for your domain
// - issue a global tls certificate in us-east-1
domain: 'sub.example.com',
hostedZoneId: 'Z1D633PJRANDOM',
globalCertificateArn: 'arn:aws:acm:us-east-1:123456789012:certificate/abcd1234-abcd-1234-abcd-1234abcd1234',

// Custom CloudFront Functions for URL rewrite
edgeFunctionFilePath: './edge.js',

// Optional: functions
enableAnalytics: true,
enableAssetCleanup: true,

// all resources created in the stack will be tagged
// tags: {
// key: 'value'
// },
};

new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
```

Run the following command to deploy stacks separately.
Run the following command to deploy stack.

```bash
npx cdk deploy --require-approval never --all --app="npx tsx stack/index.ts"
```

## Advanced

Cloudfront edge functions can be used for URL rewrite, among other use-cases.

> [!IMPORTANT]
> Check out the resources from [aws-samples/amazon-cloudfront-functions](https://github.com/aws-samples/amazon-cloudfront-functions) for examples.

## Useful commands
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
Expand Down
20 changes: 20 additions & 0 deletions cdk-spa-init.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env node
import shell from "shelljs";
import path from "path";
import fs from "fs";
import { fileURLToPath } from 'url';

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

const logPrefix = 'CDK SPA Init';

shell.echo(`${logPrefix}: Initializing CDK stack index file for a dynamic SPA...`);

if (!fs.existsSync('stack')) {
shell.mkdir('-p', 'stack');
shell.cp(path.join(__dirname, 'templates/spa.ts'), 'stack/index.ts');
shell.echo(`${logPrefix}: CDK stack index file created. Please adapt the file at 'stack/index.ts' to the project's needs.`);
} else {
shell.echo(`${logPrefix}: CDK stack folder already exists.`);
}
8 changes: 4 additions & 4 deletions examples/astro/stack/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import { App } from "aws-cdk-lib";
import { StaticSiteStack, type StaticSiteProps } from "../../../";
// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa";
import { SPAStack, type SPAProps } from "../../../";
// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa";

const appStackProps: StaticSiteProps = {
const appStackProps: SPAProps = {
env: {
account: '665186350589',
region: 'us-east-1'
Expand Down Expand Up @@ -49,4 +49,4 @@ const appStackProps: StaticSiteProps = {

};

new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
8 changes: 4 additions & 4 deletions examples/gatsby/stack/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import { App } from "aws-cdk-lib";
import { StaticSiteStack, type StaticSiteProps } from "../../../";
// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa";
import { SPAStack, type SPAProps } from "../../../";
// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa";

const appStackProps: StaticSiteProps = {
const appStackProps: SPAProps = {
env: {
account: '665186350589',
region: 'us-east-1'
Expand Down Expand Up @@ -39,4 +39,4 @@ const appStackProps: StaticSiteProps = {

};

new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
8 changes: 4 additions & 4 deletions examples/vitepress/stack.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import { App } from "aws-cdk-lib";
import { StaticSiteStack, type StaticSiteProps } from "../../";
// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa";
import { SPAStack, type SPAProps } from "../../";
// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa";

const appStackProps: StaticSiteProps = {
const appStackProps: SPAProps = {
env: {
account: '665186350589',
region: 'us-east-1'
Expand Down Expand Up @@ -36,4 +36,4 @@ const appStackProps: StaticSiteProps = {

};

new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
new SPAStack(new App(), `${appStackProps.application}-${appStackProps.service}-${appStackProps.environment}-stack`, appStackProps);
8 changes: 4 additions & 4 deletions examples/vuejs/stack.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import { App } from "aws-cdk-lib";
import { StaticSiteStack, type StaticSiteProps } from "../../";
// import { StaticSiteStack, type StaticSiteProps } from "@thunderso/cdk-spa";
import { SPAStack, type SPAProps } from "../../";
// import { SPAStack, type SPAProps } from "@thunderso/cdk-spa";

const appStackProps: StaticSiteProps = {
const appStackProps: SPAProps = {
env: {
account: '665186350589',
region: 'us-east-1'
Expand Down Expand Up @@ -35,4 +35,4 @@ const appStackProps: StaticSiteProps = {
}
};

new StaticSiteStack(new App(), `${appStackProps.application}-${appStackProps.environment}-${appStackProps.service}-stack`, appStackProps);
new SPAStack(new App(), `${appStackProps.application}-${appStackProps.environment}-${appStackProps.service}-stack`, appStackProps);
4 changes: 2 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { StaticSiteStack } from './stack/StaticSiteStack';
export type { StaticSiteProps } from './stack/StaticSiteProps';
export { SPAStack as SPAStack } from './stack/SPAStack';
export type { SPAProps as SPAProps } from './stack/SPAProps';
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@thunderso/cdk-spa",
"version": "0.5.0",
"description": "CDK Library for provisioning infrastructure for Single Page Applications.",
"tags": ["aws", "cdk", "spa", "typescript"],
"type": "module",
"main": "index.ts",
"repository": {
Expand All @@ -20,6 +21,9 @@
"clean": "tsc -b --clean",
"watch": "tsc -b -w"
},
"bin": {
"cdk-spa-init": "cdk-spa-init.mjs"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "22.4.0",
Expand Down
Loading

0 comments on commit 7f739f7

Please sign in to comment.