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 usersPOST /user
- Create a userGET /user/{id}
- Get a user by IDPUT /user/{id}
- Update a user by IDDELETE /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
Some points to consider about Carlin algorithm (you can check the documentation here):
-
The
LambdaS3Bucket
,LambdaS3Key
, andLambdaS3Version
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 theAWS::Serverless::Function
resource is the path to the method in theroute.ts
file fromsrc/
and the method name separated by a dot.
Patternsโ
-
Use the
src/api
directory to define your API resources. -
Create folders brackets
{}
to define dynamic routes. For example,src/api/user/{id}/route.ts
will be the route forGET /user/{id}
. -
Use the
route.ts
file to define the methods for the resource. -
Name your methods with the HTTP method in uppercase. For example,
GET
,POST
,PUT
,DELETE
. -
Use the
APIGatewayProxyHandler
type fromaws-lambda
to define the method signature.-
Don't forget to install
@types/aws-lambda
. -
It should be
APIGatewayProxyHandler
instead ofAPIGatewayProxyHandlerV2
because of the input format of a Lambda function for proxy integration.
-
-
Use the
Globals
property to define common properties for all functions. -
Name your CloudFormation function resources with the following pattern:
{ResourceName}{HTTPMethod}Function
. Examples:-
UsersGETFunction
:GET /users
-
UserIdGETFunction
:GET /user/{id}
-
-
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 beApiV1
.Resources:
ApiV1:
Type: AWS::Serverless::Api
Properties:
StageName: v1 -
Name the
Events
property in theAWS::Serverless::Function
resource with the name of yourAWS::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