Recently I decided to take the serverless approach to build first a “fast reading course”. This course consists of a different set of exercises to improve your reading speed.
It’s a simple database structure with a lot of info (mainly for the text information). I needed to store the student related info, the exercise texts (like a dictionary of words and several small texts), the admin/teacher info and the course structure.
I chose DynamoDB as the database (the decision to use DynamoDB and not for example MongoDB was because the cost structure on AWS, it is almost free when developing) I used Angular for the frontend (lot of experience using Angular) and AWS Lambda to run the API functions (let’s test something new!).
This was the first app I built successfully, then I had to make a CMS to build the website of the course and allow users to buy online. In order to continue using Angular as the frontend and make it SEO friendly, I had to use Angular Universal.
For the authentication I integrated the app with AWS Cognito so I could reuse the functions already created by Amazon to authenticate and deliver email when registering.
The first question I had was how to build and deploy the AWS Lambda functions. I tested SAM CLI (https://aws.amazon.com/en/serverless/sam/) , I successfully created a hello world endpoint locally but then I tested serverless.com framework and I liked it more (Reasons: I saw it as a more simple approach).
So the stack was this:
- Authentication: AWS Cognito
- Serverless.com API Deploy
- Aws Lambda
- DynamoDB as the database
- Angular Universal (Frontend and SSR)
AWS Cognito
For those who don’t know it, it’s like a series of endpoints and functions, for the auth process, that you can use again so you don’t waste time on it.
Instead of calling an API endpoint you call a cognito endpoint from your UI or inside your API.
The first question was if I needed to have my own users table or I can store all info of the users in cognito. The answer is NO you have to have the users table and store the cognito id.
That Cognito Id internally is the userSub returned by the create user endpoint. You must store that userSub as your id for the created user.
The other question was: What compatible Angular Library to use to connect to cognito?. After some time “googling” I found Amazon Cognito Identity JS ( https://www.npmjs.com/package/amazon-cognito-identity-js ), not sure why it was not easy to find. Here I found the docs https://datacadamia.com/aws/cognito/js_identity
Amazon is trying you to use AWS Amplify but I was not sure whether to use it or not. I had some code already done so I decided not to use it (Also refer to this https://peter-sereda.medium.com/why-you-may-want-not-use-aws-amplify-for-your-next-serverless-project-57b578e60252)
Other consideration is that if you want to provide a confirmation URL in your Create User API endpoint the email template must be added in the API (also for “forgot my password). That’s because in the Amazon Cognito configuration you have a limitation in the amount of characters you can enter for the email template and also it’s not dynamic.
Serverless.com
It’s a framework to easily deploy to AWS and it configures all the resources you need there. It uses YML language to declare all the services.
It has some plugins that you can use:
- serverless-bundle: it uses webpack to build your local js
- serverless-domain-manager: custom domain for your endpoints url
- serverless-offline: in order to test locally
- serverless-dynamodb-local: it open a local dynamo database
The setup was easy, so I decided to use it.
The only drawback was that after I built several endpoints, I deployed to my dev environment (in AWS, not locally) and I got the error of “More than 200 hundred resources” see here: https://www.serverless.com/blog/serverless-workaround-cloudformation-200-resource-limit/ The solution was to split into several microservices (differents YML files)
I recommend to split 1 service per DB table or by logical or so you have a maximum of 10 endpoints (ideally 5) endpoints per service file.
The serverless-base-aws has all the table and S3 bucket definitions.
After all, I’m happy with Serverless framework, it’s easy to deploy. However if I start a new project I would check first AWS CDK https://aws.amazon.com/en/cdk/ with the intention to see what’s the current best option, but as I had said I’m happy with serverless.
AWS Lambda
Serverless it’s the “deploy” framework to AWS but behind the scenes it creates several Lambda functions. Those are the real serverless functions that you can trigger calling an url that AWS provides. For the majority of projects you would have to set up a custom domain.
I’m really a fan of all these serverless approaches to build an API. You have some pros and cons but for my structure it worked really well.
The only consideration for me was to increase the memory size even if my API functions do simple things (like just updating a Dynamodb record), the reason is that more memory means more CPU speed and that increases the response time of the server.
You can check a nice article about CPU speed, memory usage and cost on AWS Lambda https://medium.com/geekculture/pick-the-right-memory-size-for-your-aws-lambda-functions-682394aa4b21
DynamoDB
DynamoDB it’s a document (json) database, so it’s easy to use in a Node.js project.
The good thing about Dynamodb is that you can have dev, stage and production database for free or at a really small fee (a really good thing for startup companies) , also it’s easy to use when you are in the AWS ecosystem.
I have used MongoDB before and, if I need to choose again, I think the only reason I will choose DynamoDB it’s because it’s free when you are developing. I think MongoDB has more features and it’s easier to query, especially geo queries.
Angular + Angular Universal
I have used Angular for production projects for 4 years now. I have no complaints. I think it’s a great framework, robust and you can build real complex apps.
I had to integrate Angular Universal into the project because it had to add visibility for search engines. I had zero experience using it so I grabbed an Angular Universal project from github and added the files into my project. After some time “fighting” with it I had my site running SEO friendly.
It’s really important to check where you are using Browser only functions or objects, like Local Storage or the window object. You have to replace those with your own implementation. Check this out for Local Storage: https://santibarbat.medium.com/how-to-use-localstorage-with-angular-9-universal-ssr-517578615637
Also it is really important to add Transfer State, which is a way to inject the data you fetch from the server and add that to the rendered app. Then, in the browser, the app will load the data from there and not call the API again. The problem is that Angular renders twice. Once on the server side, and then the second time after the browser loads the javascript code. You must stop that in order to really improve the load speed and also avoid extra charges for executing twice each endpoint.
Other consideration if you want to have a real SEO friendly site you also have to change the title per page, add to the HTML head the description and other metatags that the Website Crawlers read.
Conclusion
Building a serverless app and using all AWS services has the benefit that in development time you don’t need to pay for the resources (or almost nothing).
I literally pay 0.50 per domain in Route S3. For garage companies or experimental projects it’s really a good start.
Also you have the other benefits: A scalable structure when the project grows, amazing performance (it depends on what you can pay), a variety of server resources to complete your tasks and decide what is best for the project.
For the frontend Angular is a good framework to use, complete, you don’t need to find any other library or tool.
If you are not using Angular Universal you can deploy easily your Angular UI to Amazon S3 bucket with a serverless plugin called serverless-finch (https://www.npmjs.com/package/serverless-finch). Then access the bucket by a custom domain or subdomain