Skip to content

Instantly share code, notes, and snippets.

@gpoitch
Created November 17, 2020 04:24
Show Gist options
  • Save gpoitch/128c7676649a0061de48159b2cb28aa3 to your computer and use it in GitHub Desktop.
Save gpoitch/128c7676649a0061de48159b2cb28aa3 to your computer and use it in GitHub Desktop.
Node API Gateway Middleware
/**
* Convert a nodejs http request into an AWS API Gateway 'event' object.
* Or apply middleware to automatically convert requests/responses.
* Useful for testing your API Gateway lambda handler locally.
*/
import { IncomingMessage, IncomingHttpHeaders, ServerResponse } from 'http'
import { URL, URLSearchParams } from 'url'
import { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda'
const noop = () => {}
const MockApiGatewayContext = {
callbackWaitsForEmptyEventLoop: false,
functionName: '',
functionVersion: '',
invokedFunctionArn: '',
awsRequestId: '',
logGroupName: '',
memoryLimitInMB: '',
logStreamName: '',
getRemainingTimeInMillis: () => 100,
done: noop,
fail: noop,
succeed: noop
}
function normalizeIncomingHeaders(inParams: IncomingHttpHeaders) {
return Object.keys(inParams).reduce((params: Record<string, string>, key) => {
const value = inParams[key]
params[key] = (Array.isArray(value) ? value[0] : value) || ''
return params
}, {})
}
function normalizeOutgoingHeaders(outParams: APIGatewayProxyResult['headers']) {
return Object.keys(outParams || {}).reduce((params: Record<string, string>, key) => {
params[key] = '' + outParams?.[key]
return params
}, {})
}
function normalizeIncomingQueryParams(params: URLSearchParams) {
return [...params].reduce((obj, [key, val]) => ({ ...obj, [key]: val }), {})
}
export function nodeRequestToApiGatewayEvent(request: IncomingMessage) {
const parsedUrl = new URL(request.url || '', 'x://x.x')
const httpMethod = request.method || 'GET'
const path = parsedUrl.pathname || '/'
return {
httpMethod,
path,
headers: normalizeIncomingHeaders(request.headers),
queryStringParameters: normalizeIncomingQueryParams(parsedUrl.searchParams),
multiValueQueryStringParameters: null,
multiValueHeaders: {},
resource: '$default',
body: null,
pathParameters: null,
stageVariables: null,
isBase64Encoded: false,
requestContext: {
path,
httpMethod,
protocol: request.httpVersion,
accountId: '',
apiId: '',
domainName: '',
domainPrefix: '',
extendedRequestId: '',
authorizer: null,
requestId: '',
requestTime: '',
requestTimeEpoch: Date.now(),
resourceId: '$default',
resourcePath: '$default',
stage: '$default',
identity: {
accessKey: null,
accountId: null,
apiKey: null,
apiKeyId: null,
apiId: null,
caller: null,
cognitoAmr: null,
cognitoAuthenticationProvider: null,
cognitoAuthenticationType: null,
cognitoIdentityId: null,
cognitoIdentityPoolId: null,
principalOrgId: null,
sourceIp: '',
user: null,
userAgent: 'Mozilla/5.0',
userArn: null
}
}
}
}
export function applyApiGatewayResultToNodeResponse(result: APIGatewayProxyResult, response: ServerResponse) {
const responseHeaders = normalizeOutgoingHeaders({ ...result.headers, 'content-type': 'application/json' })
response.writeHead(result.statusCode, responseHeaders)
response.end(result.body)
}
export function apiGatewayMiddleware(handler: APIGatewayProxyHandler) {
return async function (
request: IncomingMessage,
response: ServerResponse,
next?: (request: IncomingMessage, response: ServerResponse) => void
) {
const result = (await handler(
nodeRequestToApiGatewayEvent(request),
MockApiGatewayContext,
noop
)) as APIGatewayProxyResult
applyApiGatewayResultToNodeResponse(result, response)
next?.(request, response)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment