Deploy app to AWS by using Serverless Framework [part 4]
Laurence Ho
Posted on February 3, 2024
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
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);
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
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
Now, let's deploy it to AWS Lambda:
$ 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 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
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"]
}
]
}
- Go to your Lambda function -> Configuration -> Permissions -> Execution role
- Click role and it will open another IAM tab. Update policy and attach necessary permission as above.
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.
- Go to API Gateway console -> Select your API -> API Settings -> Binary Media Types -> Click "Manage media types"
- Add
image/*
andmultipart/form-data
to the list of binary media types - Redeploy your Lambda functions or it won't take effect
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
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:
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.
Posted on February 3, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.