Deploy app to AWS by using Serverless Framework [part 4]
Tags: tutorial, webdev, aws, serverless
Published: 2024-02-03 23:36:44

This post continues from my last post [Running app locally [part 3]](https://dev.to/laurenceho/a-full-stack-vuejs-photo-album-app-part-3-4p3l)

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](https://www.serverless.com/). (This project is quite small, so I think using [SST](https://sst.dev/) is kind of overkill.)

---

## Setting up Serverless Framework

Install serverless module via NPM:

```bash
npm install -g serverless
```

When you look into [server/src/app.ts](https://github.com/LaurenceHo/quasar-photo-albums/blob/master/server/src/app.ts), you can see we import `serverless-http` into this file:

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

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

export const handler = serverless(app as any);
```

### 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](https://github.com/LaurenceHo/quasar-photo-albums/blob/master/server/serverless.yml), you might want to use different service name or region or runtime. You will also need to install [serverless-dotenv-plugin](https://www.serverless.com/plugins/serverless-dotenv-plugin). It will load environment variables from `.env` file to `serverless.yml`.

```bash
$ npm i -D serverless-dotenv-plugin
```

Your `serverless.yaml` should look like as below:

```yaml
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
```

Now, let's deploy it to AWS Lambda:

```bash
$ npx serverless deploy
```

If this is your first time using Serverless Framework, you will be asked to set up your AWS credentials. Please check [here](https://www.serverless.com/framework/docs/providers/aws/guide/credentials/) for further information.

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

```bash
> 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
```

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:

```json
{
  "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"]
    }
  ]
}
```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/einksyzjzbhfj9wald3m.jpeg)

You can also add IAM configuration to your `serverless.yml`. Please check this [doc](https://www.serverless.com/framework/docs/providers/aws/guide/iam).

### 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](https://www.serverless.com/framework/docs/providers/aws/events/http-api), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1x9iewvo56xjtckij8a.jpeg)

Please check [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings-configure-with-console.html) 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](https://docs.aws.amazon.com/apigateway/latest/developerguide/stages.html#how-to-stage-settings-console) 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
```bash
$ npm run build
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cp6j25tik7xir9ykacw7.jpeg)

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](https://dev.to/laurenceho/a-fullstack-vuejs-photo-album-app-part-1-2bgd).

### 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](https://dev.to/laurenceho/a-fullstack-vuejs-photo-album-app-part-1-2bgd).

---

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.