Skip to content

Commit 4c9eed4

Browse files
Merge pull request #5 from Mipmipp/feature/api-gateway-lambda-localstack
Feature: API Gateway and Lambda with LocalStack.
2 parents 6cccd1e + d037be5 commit 4c9eed4

File tree

7 files changed

+319
-8
lines changed

7 files changed

+319
-8
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111

1212
# LOCALSTACK
1313
/volume/*
14+
.local.env
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
#!/bin/sh
2+
3+
## LOCALSTACK API GATEWAY DOCUMENTATION RESOURCE: https://docs.localstack.cloud/user-guide/aws/apigateway/
4+
## LOCALSTACK LAMBDA DOCUMENTATION RESOURCE: https://docs.localstack.cloud/user-guide/aws/lambda/
5+
6+
# This script sets up a simple CRUD API using AWS Lambda and API Gateway in a local environment.
7+
# Steps:
8+
# 1. Create Lambda functions for GET, PUT, DELETE, POST operations.
9+
# 2. Create an API Gateway REST API.
10+
# 3. Define resources and methods for the API.
11+
# 4. Deploy the API and save the endpoint.
12+
13+
# Base endpoint for the local AWS environment
14+
BASE_ENDPOINT=http://localhost:4566
15+
16+
# API configuration parameters
17+
API_NAME=items_crud # Name of the API
18+
ROUTE_NAME=items # Route name for the API
19+
STAGE=test # Deployment stage
20+
REGION=us-east-1 # AWS region
21+
LAMBDA_ROLE=arn:aws:iam::123456789012:role/lambda-role # IAM role for Lambda
22+
23+
# Function names for different CRUD operations
24+
GET_FUNCTION_NAME=test_items_get_function
25+
PUT_FUNCTION_NAME=test_items_put_function
26+
DELETE_FUNCTION_NAME=test_items_delete_function
27+
POST_FUNCTION_NAME=test_items_post_function
28+
29+
# Define the path to the zip file in UNIX format relative to the current directory
30+
ZIP_FILE_PATH="$PWD/dist/index.zip"
31+
32+
# Convert the UNIX path to Windows format using cygpath
33+
# This is necessary because the 'aws' command expects the path in Windows format
34+
WINDOWS_PATH=$(cygpath -w "$ZIP_FILE_PATH")
35+
36+
# Log messages for successful operations
37+
GENERIC_SUCCESS_LOG="Done successfully."
38+
39+
# Function to handle failures and exit the script
40+
fail() {
41+
echo "$2"
42+
exit $1
43+
}
44+
45+
# Function to execute a command and display its output
46+
execute() {
47+
OUTPUT=$( "$@" 2>&1 ) # Capture the output and errors
48+
echo "$OUTPUT" # Display the output
49+
if [ $? -ne 0 ]; then # Check if there was an error
50+
fail 1 "Error: $OUTPUT"
51+
fi
52+
}
53+
54+
# Create the GET Lambda function
55+
echo "Creating the GET Lambda function..."
56+
execute aws --endpoint-url="${BASE_ENDPOINT}" lambda create-function \
57+
--region "${REGION}" \
58+
--function-name "${GET_FUNCTION_NAME}" \
59+
--runtime nodejs20.x \
60+
--handler index.handler \
61+
--memory-size 128 \
62+
--zip-file fileb://"$WINDOWS_PATH" \
63+
--role "${LAMBDA_ROLE}"
64+
echo ${GENERIC_SUCCESS_LOG}
65+
66+
# Create the PUT Lambda function
67+
echo "Creating the PUT Lambda function..."
68+
execute aws --endpoint-url="${BASE_ENDPOINT}" lambda create-function \
69+
--region "${REGION}" \
70+
--function-name "${PUT_FUNCTION_NAME}" \
71+
--runtime nodejs20.x \
72+
--handler index.handler \
73+
--memory-size 128 \
74+
--zip-file fileb://"$WINDOWS_PATH" \
75+
--role "${LAMBDA_ROLE}"
76+
echo ${GENERIC_SUCCESS_LOG}
77+
78+
# Create the DELETE Lambda function
79+
echo "Creating the DELETE Lambda function..."
80+
execute aws --endpoint-url="${BASE_ENDPOINT}" lambda create-function \
81+
--region "${REGION}" \
82+
--function-name "${DELETE_FUNCTION_NAME}" \
83+
--runtime nodejs20.x \
84+
--handler index.handler \
85+
--memory-size 128 \
86+
--zip-file fileb://"$WINDOWS_PATH" \
87+
--role "${LAMBDA_ROLE}"
88+
echo ${GENERIC_SUCCESS_LOG}
89+
90+
# Create the POST Lambda function
91+
echo "Creating the POST Lambda function..."
92+
execute aws --endpoint-url="${BASE_ENDPOINT}" lambda create-function \
93+
--region "${REGION}" \
94+
--function-name "${POST_FUNCTION_NAME}" \
95+
--runtime nodejs20.x \
96+
--handler index.handler \
97+
--memory-size 128 \
98+
--zip-file fileb://"$WINDOWS_PATH" \
99+
--role "${LAMBDA_ROLE}"
100+
echo ${GENERIC_SUCCESS_LOG}
101+
102+
# Retrieve the ARNs for the created Lambda functions
103+
echo "Retrieving ARN for GET Lambda function..."
104+
LAMBDA_ARN_GET=$(execute aws --endpoint-url="${BASE_ENDPOINT}" lambda list-functions \
105+
--query "Functions[?FunctionName=='${GET_FUNCTION_NAME}'].FunctionArn" --output text --region "${REGION}")
106+
echo ${GENERIC_SUCCESS_LOG}
107+
108+
echo "Retrieving ARN for PUT Lambda function..."
109+
LAMBDA_ARN_PUT=$(execute aws --endpoint-url="${BASE_ENDPOINT}" lambda list-functions \
110+
--query "Functions[?FunctionName=='${PUT_FUNCTION_NAME}'].FunctionArn" --output text --region "${REGION}")
111+
echo ${GENERIC_SUCCESS_LOG}
112+
113+
echo "Retrieving ARN for DELETE Lambda function..."
114+
LAMBDA_ARN_DELETE=$(execute aws --endpoint-url="${BASE_ENDPOINT}" lambda list-functions \
115+
--query "Functions[?FunctionName=='${DELETE_FUNCTION_NAME}'].FunctionArn" --output text --region "${REGION}")
116+
echo ${GENERIC_SUCCESS_LOG}
117+
118+
echo "Retrieving ARN for POST Lambda function..."
119+
LAMBDA_ARN_POST=$(execute aws --endpoint-url="${BASE_ENDPOINT}" lambda list-functions \
120+
--query "Functions[?FunctionName=='${POST_FUNCTION_NAME}'].FunctionArn" --output text --region "${REGION}")
121+
echo ${GENERIC_SUCCESS_LOG}
122+
123+
# Create the API Gateway REST API
124+
echo "Creating the API Gateway REST API..."
125+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway create-rest-api \
126+
--region "${REGION}" \
127+
--name "${API_NAME}"
128+
echo ${GENERIC_SUCCESS_LOG}
129+
130+
# Retrieve the API ID for the created REST API
131+
echo "Retrieving the API ID for the created REST API..."
132+
API_ID=$(execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway get-rest-apis \
133+
--query "items[?name=='${API_NAME}'].id" --output text --region "${REGION}")
134+
echo ${GENERIC_SUCCESS_LOG}
135+
136+
# Get the parent resource ID (the root resource)
137+
echo "Retrieving the parent resource ID (root resource)..."
138+
PARENT_RESOURCE_ID=$(execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway get-resources \
139+
--rest-api-id "${API_ID}" \
140+
--query 'items[?path==`/`].id' --output text --region "${REGION}")
141+
echo ${GENERIC_SUCCESS_LOG}
142+
143+
# Create a new resource under the root resource ("/items")
144+
echo "Creating a new resource under the root resource (\"/items\")..."
145+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway create-resource \
146+
--region "${REGION}" \
147+
--rest-api-id "${API_ID}" \
148+
--parent-id "${PARENT_RESOURCE_ID}" \
149+
--path-part items
150+
echo ${GENERIC_SUCCESS_LOG}
151+
152+
# Retrieve the resource ID for the newly created "/items" resource
153+
echo "Retrieving the resource ID for the newly created \"/items\" resource..."
154+
RESOURCE_ID_ALL=$(execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway get-resources \
155+
--rest-api-id "${API_ID}" \
156+
--query 'items[?path==`/items`].id' --output text --region "${REGION}")
157+
echo ${GENERIC_SUCCESS_LOG}
158+
159+
# Define the GET method for the "/items" resource
160+
echo "Defining the GET method for the \"/items\" resource..."
161+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-method \
162+
--region "${REGION}" \
163+
--rest-api-id "${API_ID}" \
164+
--resource-id "${RESOURCE_ID_ALL}" \
165+
--http-method GET \
166+
--authorization-type NONE
167+
echo ${GENERIC_SUCCESS_LOG}
168+
169+
# Define the integration for the GET method
170+
echo "Defining the integration for the GET method..."
171+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-integration \
172+
--region "${REGION}" \
173+
--rest-api-id "${API_ID}" \
174+
--resource-id "${RESOURCE_ID_ALL}" \
175+
--http-method GET \
176+
--type AWS_PROXY \
177+
--integration-http-method POST \
178+
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN_GET}/invocations \
179+
--passthrough-behavior WHEN_NO_MATCH
180+
echo ${GENERIC_SUCCESS_LOG}
181+
182+
# Create a resource for individual items ("/items/{itemId}")
183+
echo "Creating a resource for individual items (\"/items/{itemId}\")..."
184+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway create-resource \
185+
--region "${REGION}" \
186+
--rest-api-id "${API_ID}" \
187+
--parent-id "${RESOURCE_ID_ALL}" \
188+
--path-part "{itemId}"
189+
echo ${GENERIC_SUCCESS_LOG}
190+
191+
# Retrieve the resource ID for the "/items/{itemId}" resource
192+
echo "Retrieving the resource ID for the \"/items/{itemId}\" resource..."
193+
RESOURCE_ID=$(execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway get-resources \
194+
--rest-api-id "${API_ID}" \
195+
--query 'items[?path==`/items/{itemId}`].id' --output text --region "${REGION}")
196+
echo ${GENERIC_SUCCESS_LOG}
197+
198+
# Define the GET method for the "/items/{itemId}" resource
199+
echo "Defining the GET method for the \"/items/{itemId}\" resource..."
200+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-method \
201+
--region "${REGION}" \
202+
--rest-api-id "${API_ID}" \
203+
--resource-id "${RESOURCE_ID}" \
204+
--http-method GET \
205+
--request-parameters "method.request.path.itemId=true" \
206+
--authorization-type NONE
207+
echo ${GENERIC_SUCCESS_LOG}
208+
209+
# Define the integration for the GET method of an individual item
210+
echo "Defining the integration for the GET method of an individual item..."
211+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-integration \
212+
--region "${REGION}" \
213+
--rest-api-id "${API_ID}" \
214+
--resource-id "${RESOURCE_ID}" \
215+
--http-method GET \
216+
--type AWS_PROXY \
217+
--integration-http-method POST \
218+
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN_GET}/invocations \
219+
--passthrough-behavior WHEN_NO_MATCH
220+
echo ${GENERIC_SUCCESS_LOG}
221+
222+
# Define the PUT method for the "/items/{itemId}" resource
223+
echo "Defining the PUT method for the \"/items/{itemId}\" resource..."
224+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-method \
225+
--region "${REGION}" \
226+
--rest-api-id "${API_ID}" \
227+
--resource-id "${RESOURCE_ID}" \
228+
--http-method PUT \
229+
--request-parameters "method.request.path.itemId=true" \
230+
--authorization-type NONE
231+
echo ${GENERIC_SUCCESS_LOG}
232+
233+
# Define the integration for the PUT method
234+
echo "Defining the integration for the PUT method..."
235+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-integration \
236+
--region "${REGION}" \
237+
--rest-api-id "${API_ID}" \
238+
--resource-id "${RESOURCE_ID}" \
239+
--http-method PUT \
240+
--type AWS_PROXY \
241+
--integration-http-method POST \
242+
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN_PUT}/invocations \
243+
--passthrough-behavior WHEN_NO_MATCH
244+
echo ${GENERIC_SUCCESS_LOG}
245+
246+
# Define the DELETE method for the "/items/{itemId}" resource
247+
echo "Defining the DELETE method for the \"/items/{itemId}\" resource..."
248+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-method \
249+
--region "${REGION}" \
250+
--rest-api-id "${API_ID}" \
251+
--resource-id "${RESOURCE_ID}" \
252+
--http-method DELETE \
253+
--request-parameters "method.request.path.itemId=true" \
254+
--authorization-type NONE
255+
echo ${GENERIC_SUCCESS_LOG}
256+
257+
# Define the integration for the DELETE method
258+
echo "Defining the integration for the DELETE method..."
259+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-integration \
260+
--region "${REGION}" \
261+
--rest-api-id "${API_ID}" \
262+
--resource-id "${RESOURCE_ID}" \
263+
--http-method DELETE \
264+
--type AWS_PROXY \
265+
--integration-http-method POST \
266+
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN_DELETE}/invocations \
267+
--passthrough-behavior WHEN_NO_MATCH
268+
echo ${GENERIC_SUCCESS_LOG}
269+
270+
# Define the POST method for the "/items" resource
271+
echo "Defining the POST method for the \"/items\" resource..."
272+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-method \
273+
--region "${REGION}" \
274+
--rest-api-id "${API_ID}" \
275+
--resource-id "${RESOURCE_ID_ALL}" \
276+
--http-method POST \
277+
--authorization-type NONE
278+
echo ${GENERIC_SUCCESS_LOG}
279+
280+
# Define the integration for the POST method
281+
echo "Defining the integration for the POST method..."
282+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway put-integration \
283+
--region "${REGION}" \
284+
--rest-api-id "${API_ID}" \
285+
--resource-id "${RESOURCE_ID_ALL}" \
286+
--http-method POST \
287+
--type AWS_PROXY \
288+
--integration-http-method POST \
289+
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN_POST}/invocations \
290+
--passthrough-behavior WHEN_NO_MATCH
291+
echo ${GENERIC_SUCCESS_LOG}
292+
293+
# Create a deployment for the API
294+
echo "Creating a deployment for the API..."
295+
execute aws --endpoint-url="${BASE_ENDPOINT}" apigateway create-deployment \
296+
--region "${REGION}" \
297+
--rest-api-id "${API_ID}" \
298+
--stage-name "${STAGE}"
299+
echo ${GENERIC_SUCCESS_LOG}
300+
301+
# Define the endpoint for accessing the API
302+
ENDPOINT="${BASE_ENDPOINT}/restapis/${API_ID}/${STAGE}/_user_request_/items"
303+
304+
# Save the endpoint to a local environment file
305+
echo "Saving the API endpoint to the local environment file..."
306+
echo "LOCAL_API_ENDPOINT=${ENDPOINT}" >> .local.env
307+
echo ${GENERIC_SUCCESS_LOG}
308+
309+
# Output the API endpoint
310+
echo "API available at: ${ENDPOINT}"

docker-compose.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ services:
99
- "127.0.0.1:4510-4559:4510-4559"
1010
environment:
1111
- DEBUG=1
12-
- SERVICES=dynamodb
12+
- SERVICES=lambda,apigateway,dynamodb
1313
volumes:
1414
- "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
1515
- "/var/run/docker.sock:/var/run/docker.sock"

src/controller/item.controller.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const createItem = async (event: APIGatewayEvent) => {
1313
};
1414

1515
const getItem = async (event: APIGatewayEvent) => {
16-
const itemId = event.pathParameters?.id;
16+
const itemId = event.pathParameters?.itemId;
1717

1818
if (!itemId) {
1919
return createResponse(StatusCodes.BAD_REQUEST, {
@@ -33,7 +33,7 @@ const getAllItems = async () => {
3333
};
3434

3535
const updateItem = async (event: APIGatewayEvent) => {
36-
const itemId = event.pathParameters?.id;
36+
const itemId = event.pathParameters?.itemId;
3737

3838
if (!itemId) {
3939
return createResponse(StatusCodes.BAD_REQUEST, {
@@ -48,7 +48,7 @@ const updateItem = async (event: APIGatewayEvent) => {
4848
};
4949

5050
const deleteItem = async (event: APIGatewayEvent) => {
51-
const itemId = event.pathParameters?.id;
51+
const itemId = event.pathParameters?.itemId;
5252

5353
if (!itemId) {
5454
return createResponse(StatusCodes.BAD_REQUEST, {

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const handler = async (
1616
case "POST":
1717
return createItem(event);
1818
case "GET":
19-
if (event.pathParameters) {
19+
if (event.pathParameters?.itemId) {
2020
return getItem(event);
2121
}
2222
return getAllItems();

src/models/item.model.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ const ddb = new dynamoose.aws.ddb.DynamoDB({
88

99
dynamoose.aws.ddb.set(ddb);
1010

11-
const ItemModel = dynamoose.model("ItemsTable", itemSchema);
11+
const ItemModel = dynamoose.model("items", itemSchema);
1212

1313
export default ItemModel;

src/schemas/item.schema.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ const itemSchema = new dynamoose.Schema({
1616
required: false,
1717
},
1818
createdAt: {
19-
type: Date,
20-
default: () => new Date(),
19+
type: String,
20+
default: () => new Date().toISOString(),
2121
},
2222
});
2323

0 commit comments

Comments
 (0)