はじめに
元々はAPI Gatewayのv2(HTTP)とLambda(Apollo Server)を使用していましたが、API Gatewayのタイムアウト時間を延長するため、HTTP(v2) → REST(v1)に変更することになりました。
しかし、API GatewayをHTTP(v2)からREST(v1)に変更しただけだと、下記のエラーが発生します。
{"statusCode":400,"body":"Cannot read properties of undefined (reading 'http')"}
そもそもなぜエラーが発生するのか、どうすればREST(v1)を使用してApllo Serverを構築できるのかを解説していきます。
そもそもなぜエラーが発生するのか?
API GatewayからLambdaに送られる際のデータの形式を確認したところ、下記のようなデータが送られていました。
API Gateway v2(HTTP)のAPI GatewayからLambdaへのデータ例
{
"version": "2.0",
"routeKey": "POST /",
"rawPath": "/",
"rawQueryString": "",
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br",
"content-length": "162",
"content-type": "application/json",
"host": "host.com",
"user-agent": "PostmanRuntime/7.43.0",
"x-amzn-trace-id": "Root=1-67998c52-7abe39f40f8d84a393ce94e4",
"x-forwarded-for": "192.168.1.1",
"x-forwarded-port": "443",
"x-forwarded-proto": "https"
},
"requestContext": {
"accountId": "0000000000000",
"apiId": "gir39gt5m8",
"domainName": "host.com",
"domainPrefix": "host",
"http": {
"method": "POST",
"path": "/",
"protocol": "HTTP/1.1",
"sourceIp": "192.168.1.1",
"userAgent": "PostmanRuntime/7.43.0"
},
"requestId": "FISa-dktjVIROEI=",
"routeKey": "POST /",
"stage": "$default",
"time": "29/Jan/2025:02:02:58 +0000",
"timeEpoch": 1738116178654
},
"body": "{\"query\":\"query {\\n login(email: \\\"test@test.com\\\", password: \\\"password\\\") {\\n common {\\n statusCode\\n message\\n }\\n }\\n}\"}",
"isBase64Encoded": false
}
API Gateway v1(REST)のAPI GatewayからLambdaへのデータ例
{
"body": {
"query": "query {\n login(email: \"test@test.com\", password: \"password\") {\n common {\n statusCode\n message\n }\n }\n}"
},
"method": "POST",
"principalId": "",
"stage": "dev",
"cognitoPoolClaims": {
"sub": ""
},
"enhancedAuthContext": {},
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br",
"content-length": "162",
"content-type": "application/json",
"host": "host.com",
"user-agent": "PostmanRuntime/7.43.0",
"x-amzn-trace-id": "Root=1-67998c52-7abe39f40f8d84a393ce94e4",
"x-forwarded-for": "192.168.1.1",
"x-forwarded-port": "443",
"x-forwarded-proto": "https"
},
"query": {},
"path": {},
"identity": {
"cognitoIdentityPoolId": "",
"accountId": "",
"cognitoIdentityId": "",
"caller": "",
"sourceIp": "192.168.1.1",
"principalOrgId": "",
"accessKey": "",
"cognitoAuthenticationType": "",
"cognitoAuthenticationProvider": "",
"userArn": "",
"userAgent": "PostmanRuntime/7.43.0",
"user": ""
},
"stageVariables": {},
"requestPath": "/"
}
上記の内容を見ると、v2(HTTP)の方はhttpというオブジェクトデータが存在しますが、v1(REST)の方にはそのオブジェクトが存在しないため、最初のエラーが出力されていました。
対応方法
では、どうすればv1(REST)とApollo Serverをつなげるか?ですが、下記の対応が必要になります。
- ApolloServerのheaderの関数を変更
- headerの設定を
createAPIGatewayProxyEventRequestHandler()
に変更
- headerの設定を
- API Gatewayのリクエストにマッピングテンプレートを作成する
- API Gatewayのレスポンスにマッピングテンプレートを作成する
ApolloServerのheaderの関数を変更
v2(HTTP)の場合、index.jsは下記のようにcreateAPIGatewayProxyEventV2RequestHandler()
を使用して構築を行います。
createAPIGatewayProxyEventV2RequestHandler()
v1(REST)の場合
それに対し、v1(REST)の場合はcreateAPIGatewayProxyEventRequestHandler()
を使用します。
createAPIGatewayProxyEventRequestHandler()
API Gatewayのリクエストにマッピングテンプレートを作成する
API Gatewayの画面で対象のリソースを選択し、「統合リクエスト」→「編集」→「マッピングテンプレート」から下記のマッピングテンプレートを追加します
application/json
コンテンツタイプ:application/json
テンプレート本文:
#define( $loop )
{
#foreach($key in $map.keySet())
#set( $k = $util.escapeJavaScript($key) )
#set( $v = $util.escapeJavaScript($map.get($key)).replaceAll("\\'", "'") )
"$k":
"$v"
#if( $foreach.hasNext ) , #end
#end
}
#end
{
"body": "$util.escapeJavaScript($input.json('$'))",
"httpMethod": "$context.httpMethod",
"method": "$context.httpMethod",
"principalId": "$context.authorizer.principalId",
"stage": "$context.stage",
"cognitoPoolClaims" : {
"sub": "$context.authorizer.claims.sub"
},
#set( $map = $context.authorizer )
"enhancedAuthContext": $loop,
#set( $map = $input.params().header )
"headers": $loop,
#set( $map = $input.params().querystring )
"query": $loop,
#set( $map = $input.params().path )
"path": $loop,
#set( $map = $context.identity )
"identity": $loop,
#set( $map = $stageVariables )
"stageVariables": $loop,
"requestPath": "$context.resourcePath"
}
application/x-www-form-urlencoded
コンテンツタイプ:application/x-www-form-urlencoded
テンプレート本文:
#define( $body )
{
#foreach( $token in $input.path('$').split('&') )
#set( $keyVal = $token.split('=') )
#set( $keyValSize = $keyVal.size() )
#if( $keyValSize >= 1 )
#set( $key = $util.escapeJavaScript($util.urlDecode($keyVal[0])) )
#if( $keyValSize >= 2 )
#set($val = $util.escapeJavaScript($util.urlDecode($keyVal[1])).replaceAll("\\'","'"))
#else
#set( $val = '' )
#end
"$key": "$val"#if($foreach.hasNext),#end
#end
#end
}
#end
#define( $loop )
{
#foreach($key in $map.keySet())
#set( $k = $util.escapeJavaScript($key) )
#set( $v = $util.escapeJavaScript($map.get($key)).replaceAll("\\'", "'") )
"$k":
"$v"
#if( $foreach.hasNext ) , #end
#end
}
#end
{
"body": $body,
"method": "$context.httpMethod",
"principalId": "$context.authorizer.principalId",
"stage": "$context.stage",
"cognitoPoolClaims" : {
"sub": "$context.authorizer.claims.sub"
},
#set( $map = $context.authorizer )
"enhancedAuthContext": $loop,
#set( $map = $input.params().header )
"headers": $loop,
#set( $map = $input.params().querystring )
"query": $loop,
#set( $map = $input.params().path )
"path": $loop,
#set( $map = $context.identity )
"identity": $loop,
#set( $map = $stageVariables )
"stageVariables": $loop,
"requestPath": "$context.resourcePath"
}
API Gatewayのレスポンスにマッピングテンプレートを作成する
API Gatewayの画面で対象のリソースを選択し、「統合レスポンス」の全てのレスポンスに下記の設定を追加します。
「編集」→「マッピングテンプレート」を選択し、下記の内容を設定する
コンテンツタイプ:application/json
テンプレート本文:
$input.path('$.body')
上記の対応後、API Gatewayのデプロイを行い、GraphQLへのリクエストを確認してみてください
問題なければ、GraphQLのリクエストが可能になっています。
参考にした資料