LambdaからAppSyncをたたく

ちょっとしたデモ用のシステムを作っていてLambdaからAppSyncを叩いてみたメモ。

AppSyncはCognitoのUserPoolで認証を設定している。
LambdaのハンドラではCognitoの認証処理を行い、認証トークンを利用してAppSyncにアクセスする。

Lambdaのハンドラーは次のような感じで実装する。

```typescript:handler.ts
import { AuthenticationDetails, CognitoUser, CognitoUserPool } from 'amazon-cognito-identity-js';
import { Handler } from 'aws-lambda';

import gql from 'graphql-tag';
import AWSAppSyncClient from 'aws-appsync';
import {AUTH_TYPE} from 'aws-appsync/lib/link/auth-link';

// tslint:disable-next-line:no-var-requires
require('isomorphic-fetch');

export const handler: Handler = async (event, context, callback) => {
  const body = JSON.parse(event.body);

  // CognitoのUserPoolに登録したユーザのログイン情報
  const authenticationData = {
    Password: process.env.PASSWORD,
    Username: process.env.USERNAME,
  };
  const authenticationDetails = new AuthenticationDetails(authenticationData);

  const poolData = {
    ClientId: process.env.COGNITO_CLIENT_ID,
    UserPoolId: process.env.COGNITO_USER_POOL_ID,
  };
  const userPool = new CognitoUserPool(poolData);
  const userData = {
    Pool: userPool,
    Username: process.env.USERNAME,
  };
  const cognitoUser = new CognitoUser(userData);

  // AppSyncを叩くために認証トークンを取得する。
  // コールバック関数を渡すタイプの非同期処理になっているのでPromiseでくるんでAsync/Awaitで使いやすくする。
  const authenticateUser = async (): Promise<string> => {
    return new Promise((resolve, reject) => {
      cognitoUser.authenticateUser(authenticationDetails, {
        newPasswordRequired: (userAttributes, requiredAttributes) => {
          const err = new Error('newPasswordRequired');
          reject(err);
        },
        onFailure: (err) => {
          reject(err);
        },
        onSuccess: (result) => {
          const idToken = result.getIdToken().getJwtToken();
          resolve(idToken);
        },
      });
    });
  };
  const accessToken: string = await authenticateUser();

  // 以降は取得したトークンを使ってAppsyncにアクセスするところ。
  // AppSyncのSDKはPromiseに対応した実装になっているので使いやすい。
  const appsyncOpts = {
    auth: {
      jwtToken: accessToken,
      type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
    },
    disableOffline: true,
    region: process.env.APPSYNC_REGION,
    url: process.env.APPSYNC_ENDPOINT,
  };
  
  let response: {[key: string]: string|number};
  try {
    const appSyncClient = new AWSAppSyncClient(appsyncOpts); 

    // Mutationのリクエストを送信する
    const {data} = await this.client.mutate({
      mutation: gql(mutations.someMutation),
      variables: {input},
    });
    const mutatedObj = data.someMutation;

    response = {
      body: JSON.stringify({ result: 'SUCCESS', mutatedObj }),
      statusCode: 200,
    };

  } catch (error) {
    response = {
      body: JSON.stringify({ result: 'ERROR', name: error.name, message: error.message}),
      statusCode: 500,
    };
  }
  callback(null, response);
};
```

上記の例はMutationの例だけど、Queryの場合はappSyncClientがリクエストを送信する部分が次の様になるだけ。

```typescript
const {data} = await appSyncClient.query({
  fetchPolicy: 'network-only',
  query: gql(queries.getObjectById),
  variables: {id},
});
const queriedObject = data.getObjectById;
```

## 参考
- [amplify\-js/README\.md Usage Use case 4.](https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/README.md#usage)
- [AWS Lambda から AppSync の API を ぶん殴る – R3 Cloud Journey](https://blog.r3it.com/aws-lambda-to-appsync-1aa0c2f1da04)