# Dioxus AWS
- [Development](#org1151159)
- [Creating a project](#org4a4e0bb)
- [Re-Writing `main` function](#orgcd58878)
- [Installing dependancy](#org850414d)
- [`main` function](#org5e61e6c)
- [Running and testing the project](#orgbb6d4af)
- [Deployment](#orgada4ed9)
- [Setup AWS CDK](#org0f7dc9c)
- [Writing CDK Stack](#org98abf74)
- [Build and deploy application](#orgb164413)
- [Building a binary for AWS Lambda](#org216cb45)
- [Deploy AWS CDK](#org45f2978)
`dioxus-aws` crate provides a `launch` function to make `dioxus` run on AWS Serverless Stack (AWS Lambda, CloudFront and S3).
# Development
## Creating a project
- Use `dx` command.
- Currently, `dioxus-cli v0.5`, which is stable version of dioxus, is supported.
```shell
cargo install dioxus-cli --version ^0.5
dx new --subtemplate Fullstack project-name
```
## Re-Writing `main` function
### Installing dependancy
- Add `dioxus-aws`.
```shell
cargo add dioxus-aws
```
### `main` function
- Change `launch(App)` to `dioxus_aws::launch(App)`.
```rust
dioxus_aws::launch(App); // launch(App);
```
## Running and testing the project
- It is same with usage of `dioxus-cli`.
```shell
cargo add dioxus-aws
dx serve --platform fullstack
```
# Deployment
It uses AWS Lambda, S3 and CloudFront to deploy dixous application.
## Setup AWS CDK
- Setup AWS CDK to deploy application.
- For more information about AWS CDK, refer to [AWS CDK Guide](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)
- For basic tutorial of AWS CDK, refer to [the tutorial](https://docs.aws.amazon.com/cdk/v2/guide/hello_world.html)
```shell
npm install -g aws-cdk
mkdir -p fixtures/aws-cdk
cd fixtures/aws-cdk
cdk init app --language=typescript
```
## Writing CDK Stack
- `Route53` sets up a domain to CloudFront.
- `CloudFront` distributes requests to API Gateway or S3.
- `API Gateway` is bound to AWS Lambda.
- `S3` stores assets like js, html, images and so on.
```typescript
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as targets from "aws-cdk-lib/aws-route53-targets";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
export class AwsCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const domain = process.env.DOMAIN || "";
const acmId = process.env.ACM_ID || "";
const hostedZoneId = process.env.HOSTED_ZONE_ID || "";
const projectRoot = process.env.PROJECT_ROOT || "";
const assetsBucket = new s3.Bucket(this, "Bucket", {
bucketName: domain,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
const certificate = acm.Certificate.fromCertificateArn(
this,
"Certificate",
acmId,
);
const func = new lambda.Function(this, "Function", {
runtime: lambda.Runtime.PROVIDED_AL2023,
code: lambda.Code.fromAsset(projectRoot + "/dist"),
handler: "bootstrap",
environment: {
NO_COLOR: "true",
ASSETS_PATH: "./",
},
memorySize: 128,
});
const api = new apigateway.LambdaRestApi(this, "Api", {
handler: func,
proxy: true,
});
const s3Origin = new origins.S3Origin(assetsBucket);
const apiOrigin = new origins.RestApiOrigin(api);
const cf = new cloudfront.Distribution(this, "Distribution", {
defaultBehavior: {
origin: apiOrigin,
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS,
originRequestPolicy:
cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
},
additionalBehaviors: {
"/assets/*": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
"/*.js": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
"/*.css": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
"/*.html": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
"/*.ico": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
"/*.svg": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
"/icons/*": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
"/images/*": {
origin: s3Origin,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
},
domainNames: [domain],
certificate,
});
const zone = route53.HostedZone.fromHostedZoneAttributes(
this,
"zone-attribute",
{
zoneName: domain,
hostedZoneId,
},
);
new route53.ARecord(this, "IpV4Record", {
zone,
target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(cf)),
});
new route53.AaaaRecord(this, "IpV6Record", {
zone,
target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(cf)),
});
}
}
```
## Build and deploy application
### Building a binary for AWS Lambda
- Note that `server-feature` is set to `lambda` instead of `server`.
- Then, rename binary to `bootstrap`.
- Usually, `SERVICE` might be `basename $(git rev-parse --show-toplevel)`.
```shell
export SERVICE=$(cargo tree | head -n 1 | awk '{print $1}')
dx build --release --platform fullstack --server-feature lambda
mv dist/$SERVICE dist/bootstrap
```
### Deploy AWS CDK
- Let you remember environments in CDK Stack.
- `DOMAIN` is FQDN including subdomain.
- `ACM_ID` must be placed in `us-east-1` for CloudFront.
- `HOSTED_ZONE_ID` is a zone ID of Route53.
- `PROJECT_ROOT` is a path of project root.
```shell
export DOMAIN="dioxus.example.com"
export ACM_ID="arn:aws:acm:us-east-1:---"
export HOSTED_ZONE_ID="Z--"
export PROJECT_ROOT=$(pwd)
export AWS_PROFILE=default
cd fixtures/aws-cdk
npm run build
cdk synth
cdk bootstrap --profile $AWS_PROFILE
# AWS Stack deployment
cdk deploy --require-approval never --profile $AWS_PROFILE
# S3 Assets sync
aws s3 sync $PROJECT_ROOT/dist/public s3://$DOMAIN --delete --profile $AWS_PROFILE
```