Skip to content

Instantly share code, notes, and snippets.

@crypticmind
Last active March 18, 2025 16:24
Show Gist options
  • Save crypticmind/c75db15fd774fe8f53282c3ccbe3d7ad to your computer and use it in GitHub Desktop.
Save crypticmind/c75db15fd774fe8f53282c3ccbe3d7ad to your computer and use it in GitHub Desktop.
Setup lambda + API Gateway using localstack

About

Example on how to run locally an AWS Lambda via API Gateway using localstack.

Based on...

  1. https://stackoverflow.com/questions/44547574/create-api-gateway-in-localstack/48682628
  2. https://ig.nore.me/2016/03/setting-up-lambda-and-a-gateway-through-the-cli/

Usage

  1. Build a zip containing lambda.js, name it api-handler.zip
  2. Launch localstack in whatever way you can. See sample docker-compose.yml.
  3. Run setup.sh

Pending

  1. Catch any URL /api/whatever.
  2. Pass all HTTP verbs. Only GET is configured here. Other verbs require their own integration as ANY is not supported yet.
  3. No query parameters yet.
version: '2.1'
services:
localstack:
image: localstack/localstack
ports:
- "4567-4583:4567-4583"
- "${PORT_WEB_UI-4566}:${PORT_WEB_UI-8080}"
environment:
- SERVICES=${SERVICES-lambda,apigateway }
- DEBUG=${DEBUG- }
- DATA_DIR=${DATA_DIR- }
- PORT_WEB_UI=${PORT_WEB_UI- }
- LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR-docker-reuse }
- KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
'use strict'
const apiHandler = (payload, context, callback) => {
console.log(`Function apiHandler called with payload ${JSON.stringify(payload)}`);
callback(null, {
statusCode: 201,
body: JSON.stringify({
message: 'Hello World'
}),
headers: {
'X-Custom-Header': 'ASDF'
}
});
}
module.exports = {
apiHandler,
}
#!/bin/sh
API_NAME=api
REGION=us-east-1
STAGE=test
function fail() {
echo $2
exit $1
}
awslocal lambda create-function \
--region ${REGION} \
--function-name ${API_NAME} \
--runtime nodejs8.10 \
--handler lambda.apiHandler \
--memory-size 128 \
--zip-file fileb://api-handler.zip \
--role arn:aws:iam::123456:role/irrelevant
[ $? == 0 ] || fail 1 "Failed: AWS / lambda / create-function"
LAMBDA_ARN=$(awslocal lambda list-functions --query "Functions[?FunctionName==\`${API_NAME}\`].FunctionArn" --output text --region ${REGION})
awslocal apigateway create-rest-api \
--region ${REGION} \
--name ${API_NAME}
[ $? == 0 ] || fail 2 "Failed: AWS / apigateway / create-rest-api"
API_ID=$(awslocal apigateway get-rest-apis --query "items[?name==\`${API_NAME}\`].id" --output text --region ${REGION})
PARENT_RESOURCE_ID=$(awslocal apigateway get-resources --rest-api-id ${API_ID} --query 'items[?path==`/`].id' --output text --region ${REGION})
awslocal apigateway create-resource \
--region ${REGION} \
--rest-api-id ${API_ID} \
--parent-id ${PARENT_RESOURCE_ID} \
--path-part "{somethingId}"
[ $? == 0 ] || fail 3 "Failed: AWS / apigateway / create-resource"
RESOURCE_ID=$(awslocal apigateway get-resources --rest-api-id ${API_ID} --query 'items[?path==`/{somethingId}`].id' --output text --region ${REGION})
awslocal apigateway put-method \
--region ${REGION} \
--rest-api-id ${API_ID} \
--resource-id ${RESOURCE_ID} \
--http-method GET \
--request-parameters "method.request.path.somethingId=true" \
--authorization-type "NONE" \
[ $? == 0 ] || fail 4 "Failed: AWS / apigateway / put-method"
awslocal apigateway put-integration \
--region ${REGION} \
--rest-api-id ${API_ID} \
--resource-id ${RESOURCE_ID} \
--http-method GET \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN}/invocations \
--passthrough-behavior WHEN_NO_MATCH \
[ $? == 0 ] || fail 5 "Failed: AWS / apigateway / put-integration"
awslocal apigateway create-deployment \
--region ${REGION} \
--rest-api-id ${API_ID} \
--stage-name ${STAGE} \
[ $? == 0 ] || fail 6 "Failed: AWS / apigateway / create-deployment"
ENDPOINT=http://localhost:4567/restapis/${API_ID}/${STAGE}/_user_request_/HowMuchIsTheFish
echo "API available at: ${ENDPOINT}"
echo "Testing GET:"
curl -i ${ENDPOINT}
echo "Testing POST:"
curl -iX POST ${ENDPOINT}
@dalmosantos
Copy link

##Add 'awslocal'
echo -n -e'

alias awslocal="aws --endpoint-url=http://localhost:4566"' >> ~/.bashrc

@mcnowakGP
Copy link

Getting error: "NoSuchBucket The specified bucket does not exist restapis 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE"

@Arun-AG
Copy link

Arun-AG commented Feb 27, 2022

Getting error "NO such bucket"

@wvargas-ods
Copy link

wvargas-ods commented Apr 8, 2022

I had the same "No such bucket" error and I fixed it creating the URL in this way:
http://localhost:<port>/restapis/<apiId>/<stageId>/_user_request_/<path>

port: In my case was 4566 (http://localhost:4566/health), but it could be different for you.
apiId: Get the value from API_ID
stageId: Get the value from the output of the command awslocal apigateway create-deployment
path: It's what you define in the --path-part of the command awslocal apigateway create-resource

More info: https://docs.localstack.cloud/aws/apigatewayv2/

@danielnegreiros
Copy link

well, amazing, thanks!

@stokito
Copy link

stokito commented Mar 15, 2024

I wasn't able to find why the lambda path lambda:path/2015-03-31/functions/${LAMBDA_ARN}/invocations
contains the date 2015-03-31. But it looks like ignored and I set my own random date and it worked.

@vak-rashu
Copy link

It is not working, either internal server error or authentication error is coming

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment