API Gateway can invoke any AWS service directly without needing a Lambda function in between.
Let’s see this in action. Imagine you want to trigger an SQS queue when a specific API endpoint is hit. Normally, you might think: API Gateway -> Lambda -> SQS. But we can skip the Lambda.
Here’s a simplified API Gateway configuration for a POST request to /send-message that directly sends a message to an SQS queue named my-test-queue.
# Example AWS SAM template snippet
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: dev
DefinitionBody:
swagger: '2.0'
info:
title: Direct Invocation API
version: '1.0'
paths:
/send-message:
post:
summary: Send message to SQS directly
consumes:
- application/json
produces:
- application/json
parameters:
- name: body
in: body
required: true
schema:
type: object
properties:
message:
type: string
x-amazon-apigateway-integration:
type: aws
uri: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/services/sqs/actions/sendMessage
httpMethod: POST
credentials: arn:aws:iam::123456789012:role/APIGatewayInvokeSQSRole
requestParameters:
integration.request.header.Content-Type: "'application/x-www-form-urlencoded'"
requestTemplates:
application/json: |
{
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456789012/my-test-queue",
"MessageBody": "$input.json('$.message')"
}
In this example, the x-amazon-apigateway-integration block is key.
type: awstells API Gateway this is a direct AWS service integration.uripoints to the specific SQS action (sendMessage). Thelambdapart in the URI is a bit of a historical artifact for these service integrations; it doesn’t mean Lambda is involved.credentialsspecifies an IAM role that API Gateway will assume to perform the SQS action. This role needssqs:SendMessagepermissions onarn:aws:sqs:us-east-1:123456789012:my-test-queue.requestParameterssets theContent-Typeheader for the backend call.requestTemplatesis where you map the incoming API request body to the format expected by the SQSsendMessageAPI. Here, we extract themessagefield from the JSON input and use it as theMessageBody.
When a client sends a POST request to /send-message with a JSON body like {"message": "Hello SQS!"}, API Gateway will:
- Assume the
APIGatewayInvokeSQSRole. - Construct an SQS
sendMessageAPI call using theQueueUrlandMessageBodydefined in therequestTemplates. - Execute that SQS API call.
- Return the SQS API response (or an error) back to the client.
This pattern isn’t limited to SQS. You can integrate directly with many AWS services including DynamoDB, SNS, Kinesis, Step Functions, and even other API Gateways. The general structure involves setting the type to aws and providing a uri that specifies the target service and action, along with appropriate IAM credentials and request/response mappings.
The uri format for AWS service integrations is typically arn:aws:apigateway:<region>:<service_namespace>:path/YYYY-MM-DD/services/<service_name>/actions/<action_name>. For example, for DynamoDB’s PutItem, it might look like arn:aws:apigateway:us-east-1:dynamodb:path/2015-03-31/actions/putItem. The service_namespace is often lambda even though Lambda isn’t used.
The requestTemplates are crucial for transforming the incoming request payload into the structure the target AWS service expects. For DynamoDB PutItem, you’d map fields like TableName, Item, ConditionExpression, etc. For SNS Publish, you’d map TopicArn and Message.
The IAM role needs to be carefully crafted with the minimum necessary permissions. For the SQS example, it would be:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:us-east-1:123456789012:my-test-queue"
}
]
}
The most surprising thing is how infrequently this direct integration is used, often leading developers to build unnecessary Lambda functions that add latency, cost, and operational overhead for simple request-to-service-action mappings.
The x-amazon-apigateway-integration passthroughBehavior property controls how API Gateway handles requests when the requestTemplates don’t explicitly map all incoming parameters. The default is WHEN_NO_MATCH, which means if a parameter isn’t mapped, it’s passed through. NEVER will drop unmapped parameters, and WHEN_NO_TEMPLATES will pass through the entire request body if no templates are defined.
Once you’ve successfully integrated with a service, the next logical step is to handle the response from that service, often by mapping it back to the client or using it to trigger subsequent actions.