You are currently viewing A Comprehensive Guide to Building a Serverless REST API with Typescript

A Comprehensive Guide to Building a Serverless REST API with Typescript

Hello there! How’s your day going? In my last blog post, I walked you through creating a simple password generator serverless function on AWS Lambda using the Lambda console. While it’s a handy tool for getting started with serverless development, it can become cumbersome to manage multiple functions and their associated resources over time. Serverless TypeScript is a powerful combination that allows developers to build scalable and efficient serverless applications using AWS Lambda and the Serverless Framework.

So, in this post, we’ll take the same password generator function we built in the last post and show you how to build it using Typescript and the Serverless Framework . By the end of this post, you’ll have a better understanding of why using the Serverless Framework can be a game-changer for your serverless development workflow. Sounds exciting, doesn’t it? Let’s get started! But before that let’s define What we’ll cover in this post.

What we’ll cover

What is the serverless Framework?

It’s an open-source tool that simplifies the process of building, deploying, and managing serverless applications. By defining your serverless infrastructure as code, you can easily version control, manage, and collaborate on your serverless projects.

Using the Serverless Framework can significantly speed up development time and reduce the risk of errors since you can automate the deployment process and test your code in a staging environment before deploying it to production. And who doesn’t love a smoother workflow, right?

Now that we’ve talked about the Serverless Framework and how it can simplify serverless development, let’s dive into why TypeScript is a great choice for serverless development.

Why choose TypeScript for Serverless Development?

If you’re developing a serverless application, you want to choose a programming language that can help you write clean, efficient code that scales well. And that’s where TypeScript comes in!

TypeScript is a superset of JavaScript that offers additional features like static typing, interfaces, and classes. What does that mean for you? It means you can catch errors earlier in the development process, organize and structure your code more easily, and keep your codebase maintainable even as it grows.

Overall, choosing TypeScript for your serverless development can help you write better code, avoid common errors, and make your serverless workflow more efficient. So why not give it a try and see the benefits for yourself?

In the next section, we’ll show you how to build a serverless REST API with Node.js and TypeScript using the Serverless Framework. Let’s first start by configuring your aws credential.

Configure your AWS credentials

To run and manage our serverless functions, we need to :

Set up the AWS CLI

To set up the AWS CLI, follow these steps:

  • Install the AWS CLI: You can download and install the AWS CLI for your operating system by following the steps in the official docs.
  • Open a terminal window and run the aws configure command.
  • Enter your AWS access key ID and secret access key when prompted.
  • Enter the default region name and output format when prompted.
  • Test your configuration by running a command like aws s3 ls.
  • If the command runs successfully, your AWS CLI setup is complete.

Project setup

Here’s a step-by-step guide on how to set up a project with the Serverless Framework using TypeScript:

  • First, you need to have Node.js installed on your computer. You can download and install it from the official website
  • Open a terminal or command prompt and run the following command to install the Serverless Framework globally:
npm install -g serverless
  • Once the installation is complete, create a new directory for your serverless project and navigate into it and run :
serverless create --template aws-nodejs-typescript --path password-generator
  • Navigate into password-generator directory and run npm install to install the dependencies.

Project structure

The project code base is mainly located within the src folder. It contains two subfolders functions and libs.

  • functions: This subfolder contains the source code for the Lambda functions used in the project. Each function has its own subfolder containing a handler.ts file, an index.ts file that exports the function, and other files that support the function’s implementation, such as mock.json and schema.ts.
  • libs: This subfolder contains shared library code that is used across the project. It contains various utility files such as apiGateway.ts, handlerResolver.ts, and lambda.ts.
password-generator
├── src
│   ├── functions              
│   │   ├── hello
│   │   │   ├── handler.ts      
│   │   │   ├── index.ts        
│   │   │   ├── mock.json       
│   │   │   └── schema.ts      
│   │   │
│   │   └── index.ts            
│   │
│   └── libs           
│       └── apiGateway.ts       
│       └── handlerResolver.ts  
│       └── lambda.ts         
│
├── package.json
├── serverless.ts               # Serverless service file
├── tsconfig.json               # Typescript compiler configuration
├── tsconfig.paths.json         # Typescript paths
└── webpack.config.js           # Webpack configuration

The serveless.ts file is the main configuration file for the Serverless Framework, which is a powerful tool that helps you deploy and manage your serverless application. This file contains important settings that are used to deploy your application to AWS, including details about each Lambda function and other resources that your application needs.

Overall, the src folder and serverless.ts file are both critical components of a serverless project built with TypeScript and the Serverless Framework. The src folder contains the actual source code for the project, while the serverless.ts file contains the configuration information necessary to deploy the project to AWS.

In the next section, we’ll dive into creating the actual password generator Lambda function that will power your application. This is where the magic happens!

Create your password generator lambda function

To start, we’re going to remove the hello folder inside of the functions folder. In the functions folder, create a new folder called generate-password.  

Let’s create our lambda function by creating a new file called handler.ts where you will put this code : 

import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
import { formatJSONResponse } from '@libs/api-gateway';
import { middyfy } from '@libs/lambda';

import schema from './schema';

const generatePassord: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
  try {
    let length = 12;
    if (event.body.length) {
      length = parseInt(event.body.length)
    }
    const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let password = "";
    for (let i = 0; i < length; i++) {
      password += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return formatJSONResponse({ password });
  } catch (error:any) {
    return formatJSONResponse({ error: error.message }, 500)
  }
  
};

export const main = middyfy(generatePassord);

Alrighty, so this code is for a TypeScript Lambda function that generates a random password. Cool, right? Let’s dive in!

The first three lines import some modules that we’ll need for the function. Think of them like your trusty sidekicks – they’re always there to lend a helping hand when you need them. ValidatedEventAPIGatewayProxyEvent makes sure that the event being passed into the Lambda function is a valid API Gateway event, while formatJSONResponse is the function that formats the response from the Lambda function as JSON. And middyfy is like the fairy godmother who wraps the Lambda function in middleware to make it easier to work with. Abracadabra!

The fourth line imports a JSON schema that describes the expected structure of the API Gateway event. This is like a blueprint that tells the code what the event should look like. It’s like making sure the ingredients you’re using for a recipe are all there before you start cooking!

The fifth line defines the Lambda function itself, and it’s called generatePassword. It takes in an event of type ValidatedEventAPIGatewayProxyEvent<typeof schema>. This is like setting the stage for the magic trick – you’re making sure that everything is in place before you start pulling rabbits out of hats.

Inside the generatePassword function, we parse the event to get the desired length of the password. We then generate a random password using that length and a set of allowed characters. It’s like a game of Mad Libs, but for passwords!

If everything goes well, we call the formatJSONResponse function to return the password in a JSON-formatted response. And if there’s an error, we call formatJSONResponse with an error message and a 500 status code. It’s like playing a game of “Yes, And…” – if everything goes according to plan, we respond with the password, and if something goes wrong, we acknowledge the problem and try to fix it.

Finally, we wrap the generatePassword function in middleware using middyfy and export it as main. It’s like putting the finishing touches on a masterpiece and hanging it up in a gallery. Ta-da!

Let’s go over the formatJSONResponse function implementation. Go to the libs folder and replace the api-gateway.ts content with the following code bellow:

import type { APIGatewayProxyEvent, APIGatewayProxyResult, Handler } from "aws-lambda"
import type { FromSchema } from "json-schema-to-ts";

type ValidatedAPIGatewayProxyEvent<S> = Omit<APIGatewayProxyEvent, 'body'> & { body: FromSchema<S> }
export type ValidatedEventAPIGatewayProxyEvent<S> = Handler<ValidatedAPIGatewayProxyEvent<S>, APIGatewayProxyResult>

export const formatJSONResponse = (response: Record<string, unknown>, statusCode: number = 200) => {
  return {
    statusCode,
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin": "*"
    },
    body: JSON.stringify(response)
  }
}

The first few lines import some types that are used in AWS Lambda functions. The next line imports a utility type that converts a JSON schema to a TypeScript type.

The next few lines define a new type that extends the APIGatewayProxyEvent type and adds a body property with a type that is generated from the JSON schema. This type is used to validate the incoming request to the Lambda function.

The next line exports a new type that is a handler for the validated API Gateway proxy event. This type is used to handle the incoming request to the Lambda function.

Finally, there is a function that formats the response as JSON. This function takes an object and an optional status code as arguments and returns an object with the following properties: statusCodeheaders, and body.

Now that you created your lambda function, in the next section we’ll walk through the process of creating an api gateway for your lambda function using the Serverless Framework.

Create an API Gateway Endpoint for your lambda function

Creating an API Gateway endpoint is an important step in building a serverless application. This is because API Gateway acts as a front-door to your application, allowing you to control access to your backend services and provide a consistent interface for your clients.

In order for your function to work properly and be deployed to AWS, you need to define its behavior and specify the events that trigger it. 

To do so, create a new file in the same directory called index.ts and put the code bellow.

import schema from './schema';
import { handlerPath } from '@libs/handler-resolver';

export default {
  handler: `${handlerPath(__dirname)}/handler.main`,
  events: [
    {
      http: {
        method: 'post',
        path: 'generate-password',
        request: {
          schemas: {
            'application/json': schema
          }
        }
      }
    }
  ]
};

So, this code is setting up an API Gateway endpoint that generates a random password when a POST request is made to the /generate-password path. Let’s break it down!

The first line imports the JSON schema we’ll be using to validate the request. Think of this schema like a bouncer at a club – it makes sure that anyone who tries to come in meets certain requirements before they’re allowed in.

The second line imports a cool function called handlerPath from a module called @libs/handler-resolver. This function helps us resolve the path to our Lambda function handler, which is super useful because it saves us a lot of time and headache!

The next line sets up our endpoint. We start by defining our handler function and setting it to ${handlerPath(__dirname)}/handler.main. This is like the bouncer giving you a stamp on your hand that says you’re allowed to enter the club.

Then, we define the events that trigger our endpoint. In this case, we’re only setting up one event that will be triggered by an HTTP POST request to /generate-password. It’s like saying “Hey, if anyone wants to generate a password, they can make a POST request to this URL!”

Inside the http object, we define the method (POST), the path (/generate-password), and the request schema ('application/json': schema). This is like giving the bouncer at the club specific instructions on how to handle different situations.

And that’s it! We’ve set up our API Gateway endpoint to generate a random password when a POST request is made to the /generate-password path.

We talk about the schema file previously as a file that defines the structure of the JSON object that will be passed to the Lambda function. Let’s create it! Inside of the same directory, create a new file called schema.ts . And put the code bellow:

export default {
  type: "object",
  properties: {
    length: { type: 'string' }
  }
} as const;

The schema specifies that the JSON object is an object with a single property called length, which is a string. 

Now that you understand the importance of creating your Lambda function api endpoint with the Serverless Framework export your generatePassword function to make it available for other parts of your application. 

To do so, update the index.ts file inside of the functions folder by replacing the code inside of it with the following code

export { default as generatePassword } from './generate-password';

At this point, your project structure should be as follow :

password-generator
├── src
│   ├── functions               # Lambda configuration and source code folder
│   │   ├── generate-password
│   │   │   ├── handler.ts      # `generatePassword` lambda source code
│   │   │   ├── index.ts        # `generatePassword` lambda Serverless configuration
│   │   │   ├── mock.json       # `generatePassword` lambda input parameter, if any, for local invocation
│   │   │   └── schema.ts       # `generatePassword` lambda input event JSON-Schema
│   │   │
│   │   └── index.ts            # Import/export of all lambda configurations
│   │
│   └── libs                    # Lambda shared code
│       └── apiGateway.ts       # API Gateway specific helpers
│       └── handlerResolver.ts  # Sharable library for resolving lambda handlers
│       └── lambda.ts           # Lambda middleware
│
├── package.json
├── serverless.ts               # Serverless service file
├── tsconfig.json               # Typescript compiler configuration
├── tsconfig.paths.json         # Typescript paths
└── webpack.config.js           # Webpack configuration

Create your mock data for testing your Lambda function

When you’re developing a Lambda function, you may not always have access to real data that your function will be processing. That’s where mock data comes in handy. The mock.json file provides a way to simulate input data for your Lambda function so you can test its behavior and ensure that it’s working as expected.

Let’s create a mock.json file in the same directory. Put the code bellow: 

{
  "headers": {
    "Content-Type": "application/json"
  },
  "body": "{\"length\": \"12\"}"
}

Import your lambda function in the Serverless Framework configuration file

Now that we’ve created the generatePassword Lambda function, we need to import it into the Serverless Framework configuration file, serverless.ts. This will allow us to include the function in our application and deploy it to AWS. Replace the code in the serverless.ts file with the following code bellow:

import type { AWS } from '@serverless/typescript';

import generatePassword from '@functions/generate-password';

const serverlessConfiguration: AWS = {
  service: 'password-generator',
  frameworkVersion: '3',
  plugins: ['serverless-esbuild'],
  provider: {
    name: 'aws',
    runtime: 'nodejs14.x',
    apiGateway: {
      minimumCompressionSize: 1024,
      shouldStartNameWithService: true,
    },
    environment: {
      AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
      NODE_OPTIONS: '--enable-source-maps --stack-trace-limit=1000',
    },
  },
  // import the function via paths
  functions: { generatePassword },
  package: { individually: true },
  custom: {
    esbuild: {
      bundle: true,
      minify: false,
      sourcemap: true,
      exclude: ['aws-sdk'],
      target: 'node14',
      define: { 'require.resolve': undefined },
      platform: 'node',
      concurrency: 10,
    },
  },
};

module.exports = serverlessConfiguration;

The configuration file includes information about the name of your serverless application, the AWS provider and resources, the functions that are part of your application, and the packages and plugins required for your application to run smoothly.

The generatePassword function that we created earlier is also included in this file, and it is specified as part of the functions property. Additionally, the custom property includes a configuration object for the serverless-esbuild plugin, which is used to optimize your Lambda function’s code and improve performance.

Time to test your application

Now, that everything is set for deployment. It usually a best practice to test locally before deployment to AWS. Thanks to our comand line, we can simulate a test locally using the mock data we previously created. Go to the root folder of your project, open the comand line terminal and run the code bellow

sls invoke local -f generatePassword --path src/functions/generate-password/mock.json

You should see an output like the one in the screeshot bellow:

serveless typescript - local testing

Congratulations! Your function was successfully executed! 

Now that everything is set, let’s deploy our application to AWS.

Deploy your Lambda function to AWS

Now it’s time to deploy your lambda function to AWS using the command bellow:

serverless deploy

Once the deployment is completed, you should see your endpoints and function in the terminal as shown in the screenshot bellow:

serveless typescript - deployment screenshot

Congratulations! Your lambda function is up and running on AWS. 
Let’s test it by using Postman

test postman
success response
serverless typescript - succes postman

After testing, the endpoint, make sure to remove your stack on AWS so you can avoid to pay fees for all the resources you created. You can remove your service by running this command on your project directory

serverless remove

Conclusion

Congratulations! You’ve just learned how to build a password generator application using the Serverless Framework with Typescript. With this knowledge, you can build powerful and scalable serverless applications for a variety of use cases.

Throughout this tutorial, we’ve covered the basics of AWS Lambda, the Serverless Framework, and the different components of a serverless application. We’ve also explored how to create a password generator Lambda function and configure it with the Serverless Framework.

If you want to see the full source code for this application, you can find it on this GitHub repository or the original blog post. By exploring the code, you can get a better understanding of how everything fits together and build upon it to create your own serverless applications.

By leveraging the power of serverless computing, you can build applications that are highly available, scalable, and cost-effective. Whether you’re building a simple web application or a complex data processing pipeline, serverless computing can help you achieve your goals more quickly and easily.

We hope you found this tutorial helpful and that you’re inspired to build your own amazing serverless applications. Remember to experiment, learn, and have fun!

Leave a Reply