This article is written to help anyone who needs a guide in building their first serverless API on AWS using the Serverless Framework and TypeScript. If you have never built an API using the Serverless Framework, you would need to set up your development environment. Check out my post on Setting up for Serverless Development on AWS, follow the steps to download and install the components needed to complete this project.
If you are familiar with a concept or service go ahead and skip it to save yourself some time.
About this Project
This Tenant-service project represents a property-rental scenario where a tenant rents an available property and receives an SMS notification for payment. After that happens, the tenant becomes active and the property becomes unavailable to be rented by another tenant. Subsequently, an active tenant can renew the rent for their current property. Finally, an SMS notification is sent to tenants 1 month before the expiration of the rent. The operations include:
Create a tenant
Update tenant record
List all tenants by status
Delete a tenant
Record a payment transaction
Send an SMS after payment
Send a reminder via SMS, one month before the expiration
Integrated AWS Services
The Tenant service project is built on AWS as a serverless API. I have included a brief description of the AWS services used in this project and how they work. As we go on you will see how they are used in this project. These five (5) services are:
Amazon API Gateway
Amazon API Gateway receives and directs traffic to the appropriate service or handler that is responsible for that API request. Of course, such interactions must have been linked. Users can access whatever backend services you provide via the API Gateway. After the request is handled, the response is forwarded back to the API Gateway and finally, to the user. Amazon API Gateway is completely managed hence you do not need to install any server or component to start using it. It supports REST APIs as well as WebSocket APIs and also integrates easily with other supported AWS services.
AWS Lambda is one of the compute services for Serverless Computing on AWS. You can deploy a function - yes! just a function - even if the function only returns the traditional "Hello World" string, you can deploy that function to AWS Lambda and Lambda will invoke your function when it is triggered. AWS Lambda supports many programming languages but this project uses TypeScript.
Amazon DynamoDB is a NoSQL, Key-Valued based Serverless database by AWS. It is highly efficient and can power high-performance applications at scale. Given it is serverless, there's no need for provisioning - you just need to configure your table and get started using it. We will be using this database to create the table used in this project.
Dynamodb Streams is one of the cool features of the Dynamodb Store. A stream is created when an
DELETE action is carried out on a DynamoDB table. Streaming by DynamoDB has to be enabled - it is not automatically enabled. We will be using the streams to initiate checks and perform more logic based on an event in the table.
At the foundation of AWS Lambda is the concept of a trigger. A trigger is an event that causes your lambda function to be invoked or executed. An example of a trigger is a user request - when it hits Amazon API Gateway and the endpoint references a Lambda function, that function is invoked. Another way a Lambda function can be invoked is by a time-based event, similar to a cron job schedule. Amongst several uses of EventBridge, it also keeps track of schedules and triggers functions as necessary based on such schedules.
SNS is short for
Simple Notification Service. It is a pub/sub for application-to-application and application-to-person messaging systems. It is serverless hence no need for installation of any kind. You only need to configure how you want to use it. We will be using it to send SMS to the users.
Bootstrapping the Project
Execute the following command in a console to clone the project and get started.
git clone https://github.com/charlallison/tenant-service.git
After the clone operation is completed, you should have a directory containing files as rendered in the image below:
Files, Folder Structure and Project components
serverless.yml- the single most important file in the project. This file contains the configuration that the Serverless Framework uses to interact with and deploy our project to our AWS platform via our account.
package-lock.json- contains information about dependencies for our project. These include the necessary AWS dependencies, TypeScript or Node-project-specific packages used.
tsconfig.paths.json- contains configurations for the TypeScript compiler - remember, the project is written using TypeScript.
src- short for Source, refers to a folder containing
lambda- contains lambda functions and related configurations
resource- contains code (in yml) used to set up our table in DynamoDB and other configurations like streams and indexes.
The serverless.yml file
I stated that the
serverless.yml file is the most important file in the project. If that is the case, it is worth reviewing to understand its content.
Some important keys to note:
service: sets the name of the service - tenant-service here
package: packages function individually with their dependencies. However, it excludes dev dependencies.
name: specifies the name of the cloud provider - aws in this case
profile: specifies the profile with which to interact with the AWS platform - the profile contains credentials for authentication and authorization.
tracing: enables distributed tracing for all lambda functions in the project and the API Gateway
functions: specifies lambda function config files. We will see more of this in the next sessions.
resources: used to specify resources used in the project. We only have the DynamoDB table as our resource in this project. Its definition and configuration are located in the
database-table.ymlfile with the
plugins: you can call these helper function for the serverless.yml file -
esbuild: used to build the functions
serverelss-iam-roles-per-functions: used to indicate that each function can have its permission or role instead of grouping all under one.
custom: All the keys mentioned are defined by the serverless framework but there could be cases where you want a user-defined key. The custom key is where you can define a user-defined key that can be used in the other parts of the file.
Entities are a representation of real-world objects as used in source code relating to the context. Of course, there is a relationship with the database in which they are stored. For the tenant service, we have three entities that we will be working with. They contain fields that hold data and that can be used in operations.
When working with DynamoDB it is a good practice to include fields or attributes that will be used for indexing - more on this later. The following are entities in the tenant service project and their respective fields. It is worth mentioning that the indexing attributes are made up of the application attributes.
It is okay at this point to say we will be leveraging a strategy known as Single table design. This is a table design strategy where all of your data is saved to one table with no joins — more on this in subsequent parts of this article.
Three other files that contain very useful functions are the files in the
/src/libs directory. These functions are used across the service and it makes sense to have a single reference point.
api-gateway.ts: has two-fold usage:
enables schema validation with middy
formats the response message for API gateway service.
aws-client.ts: contains initialization code for AWS clients
lambda.ts: contains a middy function that chains body-parser, validator and error-handler middlewares.
We have looked at the project structure, some important files and AWS services used in this project. We now have an idea of its setup. In the next article of this series, we will look at the lambda functions and the necessary configurations needed to them deploy on AWS. Comments are certainly appreciated. If there are questions, I will try my best to answer them. I hope this was informative and thank you for sticking right on till the end.