DEV Community

Laurence Ho
Laurence Ho

Posted on • Edited on

Deploy app to AWS by using Serverless Framework [part 4]

This post continues from my last post Running app locally [part 3]

Once you can run your photo album app locally, the next step is deploying your app to AWS so you can share your photos with your friends.

When we think about AWS serverless service, the first thing that comes to our mind is Lambda function. Yes, the quickest way to deploy this backend Fastify app to AWS is to deploy it as a Lambda function. The easiest way is using Serverless Framework. (This project is quite small, so I think using SST is kind of overkill.)


Setting up Serverless Framework

Install serverless module via NPM:

npm install -g serverless
Enter fullscreen mode Exit fullscreen mode

When you look into server/src/app.ts, you can see we import serverless-http into this file:

import serverless from 'serverless-http';
...

export const app: FastifyInstance = Fastify();
...

export const handler = serverless(app as any);
Enter fullscreen mode Exit fullscreen mode

Deploy to AWS locally

Before running any serverless command, you must check serverless.yml to make sure it matches your needs. When you look into server/serverless.yml, you might want to use different service name or region or runtime. You will also need to install serverless-dotenv-plugin. It will load environment variables from .env file to serverless.yml.

$ npm i -D serverless-dotenv-plugin
Enter fullscreen mode Exit fullscreen mode

Your serverless.yaml should look like as below:

service: my-serverless-app
provider:
  name: aws
  runtime: nodejs20.x
  stage: dev
  region: us-east-1

plugins:
  - serverless-plugin-typescript #A Serverless Framework plugin to transpile TypeScript before deploying
  - serverless-dotenv-plugin #You will need this plugin to import all variables from .env into functions

useDotenv: true #Enable the plugin

functions:
  app:
    handler: src/app.handler
    events:
      - http: ANY /
      - http: ANY /{proxy+}
    environment:
      AWS_REGION_NAME: ${self:provider.region}
      GOOGLE_PLACES_API_KEY: ${env:GOOGLE_PLACES_API_KEY}
      GOOGLE_CLIENT_ID: ${env:GOOGLE_CLIENT_ID}
      ALBUM_URL: ${env:ALBUM_URL}
      IMAGEKIT_CDN_URL: ${env:IMAGEKIT_CDN_URL}
      AWS_S3_BUCKET_NAME: ${env:AWS_S3_BUCKET_NAME}
      PHOTO_ALBUMS_TABLE_NAME: ${env:PHOTO_ALBUMS_TABLE_NAME}
      PHOTO_ALBUM_TAGS_TABLE_NAME: ${env:PHOTO_ALBUM_TAGS_TABLE_NAME}
      PHOTO_USER_PERMISSION_TABLE_NAME: ${env:PHOTO_USER_PERMISSION_TABLE_NAME}
      JWT_SECRET: ${env:JWT_SECRET}

#Exclude these environment variables
custom:
  dotenv:
    exclude:
      - AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY
Enter fullscreen mode Exit fullscreen mode

Now, let's deploy it to AWS Lambda:

$ npx serverless deploy
Enter fullscreen mode Exit fullscreen mode

If this is your first time using Serverless Framework, you will be asked to set up your AWS credentials. Please check here for further information.

Once it starts deploying, you should be able to some logs in your terminal like this:

> serverless deploy

DOTENV: Loading environment variables from .env:
         - GOOGLE_PLACES_API_KEY
         - GOOGLE_CLIENT_ID
         - JWT_SECRET
         - ALBUM_URL
         - IMAGEKIT_CDN_URL
         - AWS_S3_BUCKET_NAME
         - PHOTO_ALBUMS_TABLE_NAME
         - PHOTO_ALBUM_TAGS_TABLE_NAME
         - PHOTO_USER_PERMISSION_TABLE_NAME

Deploying my-serverless-app to stage dev (us-east-1, "default" provider)
Compiling with Typescript...
Using local tsconfig.json - tsconfig.json
Typescript compiled.

... snip ...

Service Information
service: my-serverless-app
stage: dev
region: us-east-1
stack: my-serverless-app-dev
api keys:
  None
endpoints:
  ANY - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev
  ANY - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
functions:
  app: my-serverless-app-dev-app
Enter fullscreen mode Exit fullscreen mode

Congratulation! You have your first Lambda function deployed.🚀

You can check your AWS Lambda function in AWS console. You should see a new Lambda function and API Gateway created.

AWS Permissions

Because your Lambda function will access S3 bucket and DynamoDB, make sure your Lambda functions have the following permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:UpdateItem",
        "dynamodb:DeleteItem",
        "dynamodb:Scan",
        "dynamodb:Query"
      ],
      "Resource": ["YOU_AWS_DYNAMODB_TABLE_ARN"] # There should be 3 tables you created before
    },
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket"],
      "Resource": ["YOUR_AWS_S3_BUCKET_ARN", "YOUR_AWS_S3_BUCKET_ARN/*"]
    },
    {
      "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", "logs:TagResource"],
      "Effect": "Allow",
      "Resource": ["YOUR_AWS_LOG_GROUP_ARN"]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
  1. Go to your Lambda function -> Configuration -> Permissions -> Execution role
  2. Click role and it will open another IAM tab. Update policy and attach necessary permission as above.

Image description

You can also add IAM configuration to your serverless.yml. Please check this doc.

Enabling binary support using the API Gateway console

Next, you need to enable binary support using the API gateway console. Otherwise, the uploaded photos will be corrupted. I dug around in Serverless Framework doc, and I couldn't find a way to configure serverless.yml to enable binary support. It means we have to enable it using AWS admin console.

  1. Go to API Gateway console -> Select your API -> API Settings -> Binary Media Types -> Click "Manage media types"
  2. Add image/* and multipart/form-data to the list of binary media types
  3. Redeploy your Lambda functions or it won't take effect

Image description

Please check here for further information.

Enable API Gateway Stage Logging

If your API Gateway returns an HTTP 502 status code, you can enable API Gateway stage logging by updating the stage setting to get more information.
Please check here for further information.


Deploy Quasar frontend APP to AWS S3 bucket

Update environment variables

After deploying your Fastify app to Lambda, you will have an API Gateway endpoint. Put this API endpoint into .env under root folder for Quasar app. Upate AWS_API_GATEWAY_URL with your real API endpoint, it looks like https://{xxxxxxxxxx}.execute-api.{AWS region}.amazonaws.com/dev/api

Build Quasar app and upload to S3

Build your app

$ npm run build
Enter fullscreen mode Exit fullscreen mode

You will find generated files are located in the /dist folder. Upload everything in the /dist/spa folder to S3 bucket you created before.

Your file structure will look like this:

Image description

If everything is set up correctly, you now should be able to open your web app from S3 and your web app will also call the API gateway endpoint. You can find your static web hosting URL in the Properties tab.

If you cannot see anything when accessing your S3 static web hosting URL, you might need to check CORS policy. You will need to add static web hosting URL into CORS policy. You can check my previous tutorial here.

Update Google OAuth 2.0

If you want to log in from the website you deployed, you will need to update Google OAuth 2.0 or it won't work because the S3 URL is not allowed to call Google OAuth API. Remember to add your S3 bucket URL into Authorised JavaScript origins and Authorised redirect URIs as I demoed in the previous post.


In this tutorial, the demo website URL is http://demo-quasar-photo-albums.s3-website-us-east-1.amazonaws.com/albums

(Don't try to log in from this demo website, because I already omit login information. 😜)


In my next post, I'd like to demo how to optimise this photo album app by using AWS CloudFront and ImageKit CDN.

Top comments (0)