This year I had very long 'covid' vacations. Once I come back, I've started working on a new project that you can find on my Github:
https://github.com/stokilo/aws-sst-typescript-nuxtjsThis is a small SPA with a login and contact page. Authentication is build using Facebook/Google OAuth2 protocol.
The backend has been written in the Typescript. The infrastructure and development are based on the serverless stack (SST) framework.
The frontend is done with the Vue, on top of that NuxtJS, and few plugins for image optimization, sitemaps, etc.
UI components and CSS: Bulma and Buefy libraries.
The application allows to create a single 'contact' entity in DynamoDB and retrieve a list of them for the currently authenticated user. Nothing fancy going here. The code can be used as a startup template for a small project.
The website you are reading now consists of a blog and a sample serverless app (see link on the top). It is implemented using the Chalice framework where only Python language is supported.
After I've discovered SST and live lambda reload + debugging features I wanted to migrate to javascript. My motivation for doing that:
Most important for me were 1 and 2. To share the models, I've decided to keep them on the backend.
https://github.com/stokilo/aws-sst-typescript-nuxtjs/tree/master/src/backend-frontendMapping is configured under frontend/tsconfig.json file:
Here I map backend-frontend folder as @backend, you can access models in the client with the following import
This model sharing saves time in development. Additionally, I don't have to maintain converters on both sides. This also leads to fewer bugs as forgetting to update one of the sides is not uncommon.
This is not the end of the story. To keep the model in order, I've introduced the Typescript Zod library for schema validation
https://github.com/colinhacks/zodPlease check the following code snippet that depicts declaration of the Zod type
Zod schema declares model object fields, types, and validations.
Schema can be used to define a type (see TypeOf function usage).
You can pass an object to the function parse:
In the given example Zod will throw an error. Mandatory fields are not present in provided object argument. The output of the error contains a path to the missing fields and failed checks.
Having TypeOf in place gives us a model type available for static checks and nice IDE autocompletion. I've integrated Zod library in all places where data is exchanged between client and server side. For example, generic axios functions for get and post requests are using schema like following
In order to use it i.e. to fetch Contact form data from the server, you must provide schema and returned type:
For simple CRUD applications, it covers a lot of usecases. I did however a brave move and integrated Zod on the backed side (inside DAO layer for DynamoDB).
This time, I've decided to implement my own data access layer for DynamoDB.
My previous projects were including PynamoDb library for Python. Nothing wrong with that, but this time, I wanted to store data for my entities as object literals. I've converted them into DynamoDb map column type. API access is done using DynamoDb Document Client.
I wanted to keep the database schema as simple as possible. I don't like storing each entity field as a DynamoDb column. My entity's objects are small. I calculated that fetching all fields for reading and writing will not cost more RCU/WCU.
In return, I simplified DAO layer and entity mapping. Code is easier to read. Adding new functionalities takes less time. I validate the objects on the database read and writes.
Using my own abstraction for the DAO required to choose a strategy for data storage.
For this simple CRUD, it is a standard single table strategy with entities supporting the following access patterns
You may think this is a very limited set of functionalities. And this is true.
For such a simple application it is more than enough. Additionally, I think this is a good starter to learn about RCU/WCU costs while doing any design decisions. You can enable DynamoDb logs to get JSON stats included in the CloudWatch logs. Use Athena to find your RCU/WCU consumed and analyze the access patterns with associated costs.
Infrastructure is provisioned with the SST framework.
Check their Slack channel, they have an active and friendly community. Maintainers are online quite often and are very supportive.
The Serverless Stack includes a lot of interesting tutorials for serverless applications. Check them out, there is a guide for serverless application development. It is a thorough introduction with many useful tricks and tips. Their SST documentation contains many examples of how to configure your stack. I've copied most of my stack from these samples and adjusted it to my needs.
My stack deploys a full serverless application. The frontend is hosted on a predefined domain and cached on the Cloudfront. An initial deployment takes 10-20 minutes. SST CLI is very stable and quite fast. You may find some problems if you integrate various AWS services but join the Slack and ask for guidance to find a solution.