Published on

Create your own QR Code Generator with AWS and AWS CDK! - Deploying your website and connecting it with your API

Authors
  • avatar
    Name
    Katherine Moreno
    Twitter

Introduction

This post continues our journey left from our previous blog

In this one we will deploy our website by using S3 and Cloudfront, if you want to know how you can visit my other post

And also we will know how to allow to call to our API from our website by using CORS.

The architecture

alt text

How I did it

Our Stack should now be including the definition of S3 with Cloudfront.

from aws_cdk import (
    CfnOutput,
    Stack,
    aws_lambda as lambda_,
    aws_apigateway as apigateway,
    aws_s3 as s3,
    aws_s3_deployment as s3deploy,
)
from aws_cdk.aws_cloudfront import BehaviorOptions, Distribution, OriginAccessIdentity

from aws_cdk.aws_cloudfront_origins import S3Origin

from aws_cdk.aws_lambda_python_alpha import (
    PythonFunction,
) 

from constructs import Construct

import os


class QrGeneratorStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # -------------- Frontend ------------------#

        # Create a bucket to host the website
        qr_website_bucket = s3.Bucket(
            self, "qr_website_bucket", access_control=s3.BucketAccessControl.PRIVATE
        )

        # Verify if the folder exists
        website_dir = os.path.join(os.path.dirname(__file__), "website")
        if not os.path.isdir(website_dir):
            raise FileNotFoundError(f"El directorio {website_dir} no existe")

        qr_origin_access_identity = OriginAccessIdentity(self, "OriginAccessIdentity")
        qr_website_bucket.grant_read(qr_origin_access_identity)

        qr_distribution = Distribution(
            self,
            "Distribution",
            default_root_object="website.html",
            default_behavior=BehaviorOptions(
                origin=S3Origin(
                    qr_website_bucket, origin_access_identity=qr_origin_access_identity
                )
            ),
        )

        s3deploy.BucketDeployment(
            self,
            "DeployWebsite",
            sources=[s3deploy.Source.asset(website_dir)],
            destination_bucket=qr_website_bucket,
            distribution=qr_distribution,
            distribution_paths=["/*"],  # This is for invalidation of the cache
        )

        # This makes to print in the console log the domain of the distribution
        CfnOutput(self, "DomainName", value=qr_distribution.domain_name)

        # ------------------------ Backend -------------------------------------#

        # Create the s3 Bucket
        bucket = s3.Bucket(
            self,
            "Bucket",
            block_public_access=s3.BlockPublicAccess(
                block_public_acls=False,
                block_public_policy=False,
                ignore_public_acls=False,
                restrict_public_buckets=False,
            ),
            object_ownership=s3.ObjectOwnership.OBJECT_WRITER,
        )

        cf_domain_name = "https://" + qr_distribution.domain_name

        # Create the lambda
        create_qr_lambda = PythonFunction(
            self,
            "CreateQrCodeLambda",
            function_name="createQrCodeLambda",
            runtime=lambda_.Runtime.PYTHON_3_11,
            index="create_qr.py",  # Path to the directory with the Lambda function code
            handler="handler",  # Handler of the Lambda function
            entry="./lambda",
            environment={
                "REGION": self.region,
                "BUCKET_NAME": bucket.bucket_name,
                "ALLOWED_ORIGIN": cf_domain_name,
            },
            description="Lambda for creating the QR code",
        )

        bucket.grant_read_write(create_qr_lambda)
        bucket.grant_put_acl(create_qr_lambda)

        # Define the API Gateway
        api = apigateway.RestApi(self, "ApiGatewayQR", rest_api_name="RestAPI QR")

        qr_code_integration = apigateway.LambdaIntegration(create_qr_lambda)

        qr = api.root.add_resource("qr")
        #Adding Cors
        qr.add_cors_preflight(
            allow_methods=["POST", "OPTIONS"],
            allow_origins=[cf_domain_name],
        )
        qr.add_method("POST", qr_code_integration)

Now Pay attention to this part, this is what we will allow us to enable CORS to the API Gateway level

        qr.add_cors_preflight(
            allow_methods=["POST", "OPTIONS"],
            allow_origins=[cf_domain_name],
        )

Also at the level of our lambda we need to update our lambda to be more restrictive and only allow requests from a browser only if the browser is the one that we've defined on the "Access-Control-Allow-Origin" field.

...

    return {
        ...
        "headers": {
            "Access-Control-Allow-Headers": "Content-Type",
            "Access-Control-Allow-Origin": os.environ["ALLOWED_ORIGIN"],
            "Access-Control-Allow-Methods": "OPTIONS,POST",
        },
    }

Epilogue

We've deployed a website which interacts with our backends and allow to users to generate QR Codes.