Skip to main content

REST API

This document outlines the guidelines for building REST APIs.

AWS Serverless Application Modelโ€‹

This guide is for building REST APIs with AWS Serverless Application Model.

Project Structureโ€‹

The project structure was inspired by Next.js App Router routing. We have a src directory that contains a folder named api where we define our API resources as folders and a file name route.ts (following Next.js definition) that contains the methods for the resource .

Consider an API that has a CRUD operation for users with the endpoints:

  • GET /users - Get all users
  • POST /user - Create a user
  • GET /user/{id} - Get a user by ID
  • PUT /user/{id} - Update a user by ID
  • DELETE /user/{id} - Delete a user by ID

The project structure would look like this:

.
โ”œโ”€โ”€ src
โ”‚ โ”œโ”€โ”€ api
โ”‚ โ”‚ โ”‚ user
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ {id}
โ”‚ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ route.ts
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ route.ts
โ”‚ โ”‚ โ”œโ”€โ”€ users
โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ route.ts

Each route.ts file contains the methods for the resource.

  • src/api/users/{id}/route.ts

    import type { APIGatewayProxyHandler } from 'aws-lambda';

    export const GET: APIGatewayProxyHandler = async (event, context) => {
    const id = event.pathParameters?.id;

    // Get all users
    };

    export const PUT: APIGatewayProxyHandler = async (event, context) => {
    const id = event.pathParameters?.id;
    const body = JSON.parse(event.body || '{}');

    // Update a user by ID
    };

    export const DELETE: APIGatewayProxyHandler = async (event, context) => {
    const id = event.pathParameters?.id;

    // Create a user
    };
  • src/api/users/route.ts

    import type { APIGatewayProxyHandler } from 'aws-lambda';

    export const GET: APIGatewayProxyHandler = async (event, context) => {
    // Get all users
    };
  • src/api/user/route.ts

    import type { APIGatewayProxyHandler } from 'aws-lambda';

    export const POST: APIGatewayProxyHandler = async (event, context) => {
    const body = JSON.parse(event.body || '{}');

    // Create a user
    };

CloudFormationโ€‹

Following Carlin instructions to deploy resources with Lambda, you need to create your AWS::Serverless::Function as follows in your template:

AWSTemplateFormatVersion: '2010-09-09'

Transform: 'AWS::Serverless-2016-10-31'

# Define all the common properties for all functions
Globals:
Function:
CodeUri:
Bucket: !Ref LambdaS3Bucket
Key: !Ref LambdaS3Key
Version: !Ref LambdaS3Version
Runtime: nodejs20.x

Resources:
ApiV1:
Type: AWS::Serverless::Api
Properties:
StageName: v1

UsersGETFunction:
Type: AWS::Serverless::Function
Properties:
Events:
ApiV1:
Type: Api
Properties:
Path: /users
Method: GET
RestApiId: !Ref ApiV1
Handler: api/users/route.GET

UserPOSTFunction:
Type: AWS::Serverless::Function
Properties:
Events:
ApiV1:
Type: Api
Properties:
Path: /user
Method: POST
RestApiId: !Ref ApiV1
Handler: api/user/route.POST

UserIdGETFunction:
Type: AWS::Serverless::Function
Properties:
Events:
ApiV1:
Type: Api
Properties:
Path: /user/{id}
Method: GET
RestApiId: !Ref ApiV1
Handler: api/user/{id}/route.GET

UserIdPUTFunction:
Type: AWS::Serverless::Function
Properties:
Events:
ApiV1:
Type: Api
Properties:
Path: /user/{id}
Method: PUT
RestApiId: !Ref ApiV1
Handler: api/user/{id}/route.PUT

UserIdDELETEFunction:
Type: AWS::Serverless::Function
Properties:
Events:
ApiV1:
Type: Api
Properties:
Path: /user/{id}
Method: DELETE
RestApiId: !Ref ApiV1
Handler: api/user/{id}/route.DELETE
note

Some points to consider about Carlin algorithm (you can check the documentation here):

  • The LambdaS3Bucket, LambdaS3Key, and LambdaS3Version are the S3 bucket, key, and version where Carlin uploads the Lambda code and adds them as parameters in the CloudFormation template.

  • The Handler property in the AWS::Serverless::Function resource is the path to the method in the route.ts file from src/ and the method name separated by a dot.

Patternsโ€‹

  1. Use the src/api directory to define your API resources.

  2. Create folders brackets {} to define dynamic routes. For example, src/api/user/{id}/route.ts will be the route for GET /user/{id}.

  3. Use the route.ts file to define the methods for the resource.

  4. Name your methods with the HTTP method in uppercase. For example, GET, POST, PUT, DELETE.

  5. Use the APIGatewayProxyHandler type from aws-lambda to define the method signature.

    1. Don't forget to install @types/aws-lambda.

    2. It should be APIGatewayProxyHandler instead of APIGatewayProxyHandlerV2 because of the input format of a Lambda function for proxy integration.

  6. Use the Globals property to define common properties for all functions.

  7. Name your CloudFormation function resources with the following pattern: {ResourceName}{HTTPMethod}Function. Examples:

    1. UsersGETFunction: GET /users

    2. UserIdGETFunction: GET /user/{id}

  8. Name your AWS::Serverless::Api resources with the following pattern: Api{StageName}.

    For example, if you have a stage named v1, the resource name should be ApiV1.

    Resources:
    ApiV1:
    Type: AWS::Serverless::Api
    Properties:
    StageName: v1
  9. Name the Events property in the AWS::Serverless::Function resource with the name of your AWS::Serverless::Api resource. This is useful for cases in which you have multiple APIs in your template and want to use the same function for different APIs.

    Resources:
    ApiV1:
    Type: AWS::Serverless::Api
    Properties:
    StageName: v1

    ApiV2:
    Type: AWS::Serverless::Api
    Properties:
    StageName: v2

    UsersGETFunction:
    Type: AWS::Serverless::Function
    Properties:
    Events:
    ApiV1:
    Type: Api
    Properties:
    Path: /users
    Method: GET
    RestApiId: !Ref ApiV1
    ApiV2:
    Type: Api
    Properties:
    Path: /users
    Method: GET
    RestApiId: !Ref ApiV2
    Handler: api/users/route.GET