A simple web-service by DynamoDB, Lambda, CloudFront and S3
Several months ago, I made a simple web service which is a kind of “Drawing an Oracle” function.
In this article, I will show you how it is like and how it is made.
Check the actual app before you go on, if you’d like to.
Motivation
- I wanted that product
In the beginning of 2020 (last year), I and my coworker printed oracles in which encouraging quotes are written, and presented them to my coworkers/business partners.
This year (2021), however, we couldn’t do the same due to COVID-19. This made me think of creating a web based app where people can draw an oracle with quotes.
2. My study for technology stacks such as
- IaC for AWS Lambda using Serverless Framework
- Configure serverless public API with AWS
- DynamoDB
- CloudFront + S3 hosting
- Nuxt/Typescript
The Product
The final output is a web site where you can draw an oracle by clicking “Draw an Oracle” button.
Quotes varies more than 300 and show up randomly.
API: https://github.com/YutaFujii0/serverless-quote-service
Client: https://github.com/YutaFujii0/quote-with-you
Before start I was thinking of
- Configuring CI/CD to focus on developing
- Making the product accessible by multiple language
The steps to build
- Purchase my own domain
- Configure CI/CD to deploy my hello world Nuxt app to AWS S3
- Set up CloudFront, Route53 record to host static website
- Create Serverless Framework managing Lambda and DynamoDB
- Set up API Gateway and Route53 record to serve API
- Implement functions for API (Japanese)
- Create mock (design)
- Implement front-end stuff (Japanese)
- I18n (English) for both API, client
- Introduce error detection, Google Analytics
I focused on deploying hello world level app/API first before I actually implement functions needed for the product. This is because I’m quite new to some of the field that I’d like to make sure they work in production.
That’d be said, for readers’ perspective, I’d blog them down first API and then client development.
Build an API
API is structured with DynamoDB, Lambda, API Gateway and Route53. For only seeding purpose, Lambda has access to S3 as well.
I chose DynamoDB as database because
- I wanted to use
- It costs pretty much zero
- It is fully managed
Of course RDB would be a choice since the app has just a simple record-selecting process. Not to mention you can do this without having database.
In building serverless framework, I mainly follow the official blog.
Building a REST API in Node.js with AWS Lambda, API Gateway, DynamoDB, and Serverless Framework
In addition to the blog, I implemented something like
- BatchWrite access to DynamoDB
- S3 access and IaC for S3
- Use HTTP API of API Gateway rather REST API
- Logic for picking record randomly
Create Database
I don’t know any better resource than re:Invent 2018 session. The speaker said it’d be better to configure table according to access patterns of your application.
This time I had only a single access pattern.
- Pick a record randomly
- Language is specified by request
One of the most interesting issue was how to implement randomly-picking logic.
- Request total record count first and query by integer id to pick ?
- Partition by language and select single record using string id comparison ?
I googled to find the solution in stack over flow. Put it simply, what distinguishes pool to pick is set as partition key and UUID is used as sort key.
Partition Key: lang
Sort Key: uuid
Some might prefer to use uuid as partition key and create GSI, which itself is reasonable. This time I assumed only single access pattern, so I didn’t take that idea.
Here’s what serverless.yml
file looks like
Endpoint for seeding
Now I implemented an endpoint for seeding data. Seed file is csv formatted and I put it on S3 by myself.
AWS resources are managed like this:
Lambda event handler (Node.js) looked like:
Endpoint for picking quote record randomly
Finally it’s time to create the event handler to pick a quote randomly. I went through the same as seeding endpoint.
The core Lambda function is below:
Deploy
Deploying the functions is quite easy. Just sls deploy
does the trick. I automated the deploying procedures with Github action/CircleCI.
Build Front-end application
I created Nuxt app and generated static assets which is stored in S3.
Enabled Static Website Hosting and connect it with CloudFront, that’s all.
UX / UI
Let’s move on to how I designed user experience and user interface.
User Experience
- Diversity and Inclusion -> I18n support
- Quote is noble -> wide white margin, white glows
- You’ll talk your oracle to your friends -> make it sharable
- Waiting enhances excitement -> intentionally keep user wait
User Interface
- 2021 color by PANTONE
- Interaction components are white colored for high contrast to background
- Remove inaccurate-timing affordance -> hide sharing button until oracle shows up
The cheery on the cake
- Add vertically lined words so that users can feel the site like a posture of art exhibition
(I have to say my art grade was 2 out of 5 though)
OGP, favicon
I was thinking of display ogp differently from locales to locales, but after some struggle with that, I gave up that idea.
Below area the candidates of ogp, made with canva.
Once you create them, don’t forget to check if they can be seen with proper width;height.
Nuxt app
The client app is not much complicated, and libraries I used in nuxt app are:
- nuxt-i18n
- axios
- sentry
- fontawesome
- gtm
- webfontloader
- style-resources
There’s no implementation for plugins, middleware or store. Maybe the app is a bit too simple to say I learned nuxt/typescript.
Components are also simple.
components
├── atoms
│ ├── VButton.vue
│ ├── VButtonFacebook.vue
│ └── VButtonTweet.vue
├── molecules
└── organisms
└── QuoteBox.vue
layouts
└── default.vue
pages
└── index.vue
The cherry on the cake
- Deliberately set 1 second waiting before displaying quotes/author
Stylesheet
Basically I wrote CSS according with design.
challenges:
- Patched different
font-family
for each locale. Because I couldn’t find a way to alter font-family whennuxt generate
is run, I managed to do this in component computed property. - Responsive design
- Loading icon (borrowed by codepen)
- Use as much as defined properties
# assetes/stylesheets/_grid.scss
$container-max-width: 1000px;
$distance-xxl: 120px;
$distance-lg: 60px;
$distance-md: 40px;
$distance-sm: 20px;
$distance-xs: 10px;
# assetes/stylesheets/_font.scss
$font-xl: 36px;
$font-lg: 24px;
$font-md: 18px;
$font-sm: 12px;
$font-xs: 9px;
...
Struggled to implement this as well… 😅
Deploy
I followed the official docs which uses gulp. Automated with CircleCI as well.
Deploy Nuxt on Amazon Web Services
And more…
- I18n implementation was quite easy for both API and client since I designed architecture/libraries for that at the beginning
- I found bug in iOS at the last minute before I release, which I resolved by using Lambda Test
- Configure DockerHub account to avoid docker image pull limit
- Seed collection
Well, it’s done! 🎉
After a long way, finally the website is released!
Here’s what I learned.
It cost only less than penny a month
After a month and I checked my billing in AWS, which made me so happy. Without serverless architecture, it would cost at least 10 dollar a month. So cool.
User feedback
- People in my company chatted with their oracle/quotes in slack channels ☺️
- Some managers used this in their 1on1 meetings 🤩
- And my colleague of engineer said to me “Hey Yuta-san, it seems that the source code is displayed for some reason”, pointing to this 😭
THAT’S NOT A BUG, BUT A DESIGN!!!
13–0647 Illuminating
17–5104 Ultimate Gray
( PANTONE color of the 2021 )
Just for your information, they are color code PANTONE released as color of year 2021.
Anyway, it was fun to see how people use the website and hear user feedbacks.
Wrap up
- It was fun to create a product, but
- Don’t align letters vertically 😂
Thank you for reading all the way long!!
And I hope this year will be a great year to you.