Serverless Frameworkを使ってTypeScript製LambdaからAppSyncを叩くAPIをつくる

5 min read

ユースケース

AmplifyとかでAppSyncを使ったサーバレスなウェブアプリケーションは構築したけど、そのDBにバッチでゴニョゴニョするサムシングが欲しいなー。うーんアプリケーション内で実装すると煩雑になるし。。。

せや!Lambdaに切り分けたろ!Serverless Frameworkの出番や!

手順

  1. 雑にserverless createでAPI Gateway+Lambdaのアプリケーションをつくる
  2. アプリからAppSyncを叩いてDBにアクセスする
  3. Userを作成してみる

完成物

https://github.com/masatsch/serverless-appsync

環境

  • Serverless Framework
  • TypeScript
  • Node.js 14.x

つくっていく

0. Serverless Frameworkの環境構築

$ npm i -g serverless

1. TypeScript用のテンプレをつくる

$ sls create -t aws-nodejs-typescript -p serverless-playground

2. GraphQL Clientの実装

$ npm i apollo-cache-inmemory aws-appsync graphql-tag isomorphic-fetch uuid

次に、src/functions/hello/graphqlというフォルダを用意し、Amplifyなどで生成されたGraphQLの型をぶち込みます

中身はここを参考にしてみてください

https://github.com/masatsch/serverless-appsync/tree/master/src/functions/signup/graphql

3. handler.tsの実装

src/functions/hello/handler.ts
import 'source-map-support/register';

import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/apiGateway';
import { formatJSONResponse } from '@libs/apiGateway';
import { NormalizedCacheObject } from 'apollo-cache-inmemory';
import { middyfy } from '@libs/lambda';
import { AWSAppSyncClient, AUTH_TYPE } from 'aws-appsync';
import schema from './schema';
import * as api from './graphql/Model';
import * as mutations from './graphql/mutations';
import * as queries from './graphql/queries';
import 'isomorphic-fetch';

const gql = require('graphql-tag');
const env = require("process").env;
const region = env.AWS_REGION;
const appSyncUrl = env.ENDPOINT_URL;
const apiKey = env.API_KEY;

const hello: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
    const appSyncClient = new AWSAppSyncClient({
        url: appSyncUrl,
        region,
        auth: {
            type: AUTH_TYPE.API_KEY,
            apiKey: apiKey,
        },
        disableOffline: true
    });

    try {
	// Userを作成
        const createUserInput: api.CreateUserInput = {
            cognito_username: 'tmp',
            email: event.body.email,
            role: event.body.role,
        };
        const createUserResponse = await createUser(appSyncClient, createUserInput);
        const userId = createUserResponse.data.createUser.id;
        console.log(`userId is: ${userId}`);

        // 作成したUserを検索
        const listUsersFilter: api.ModelUserFilterInput = {
            id: { eq: userId },
        };
        const user = await getUser(appSyncClient, listUsersFilter);

        // 返り値
        return formatJSONResponse({
            result: user,
        });
    } catch (err) {
        return formatJSONResponse({
            result: err,
        });
    };
};

// Userを作成する非同期関数
const createUser = async (appSyncClient: AWSAppSyncClient<NormalizedCacheObject>, input: api.CreateUserInput): Promise<api.Output<api.CreateUserMutation>> => {
    const createUserResponse = await appSyncClient.mutate({
        mutation: gql(mutations.createUser),
        variables: { input: input },
    }) as api.Output<api.CreateUserMutation>;

    return createUserResponse;
}

// Userを検索する非同期関数
const getUser = async (appSyncClient: AWSAppSyncClient<NormalizedCacheObject>, filter: api.ModelUserFilterInput): Promise<api.User | undefined> => {
    const listUsersResponse = await appSyncClient.query({
        fetchPolicy: 'network-only',
        query: gql(queries.listUsers),
        variables: filter,
    }) as api.Output<api.ListUsersQuery>;

    const user = listUsersResponse.data.listUsers.items[0] as api.User | undefined;
    return user;
}

export const main = middyfy(hello);

おわり

Ex 1. node_modulesをlayerにする

$ npm install -D serverless-layers
serverless.ts
    ...
    custom: {
        ...
        'serverless-layers': {
            layersDeploymentBucket: 'sample-bucket', // layerをuploadするS3 Bucket名
            dependenciesPath: './package.json',
        },
    },
    ...
    plugins: [
        'serverless-webpack',
        'serverless-layers', // ここを追加
    ],

Ex 2. API GatewayにAPI Keyを指定する

$ npm install -D serverless-add-api-key
serverless.ts
    ...
    custom: {
        ...
        apiKeys: [
            {
                name: 'sample', // API Keyの名前
            },
        ],
    },
    ...
    plugins: [
        'serverless-webpack',
        'serverless-layers',
        'serverless-add-api-key', // ここを追加
    ],
src/functions/hello/index.ts

export default {
    ...
    environment: { // これを追加
        API_KEY: process.env.API_KEY,
        ENDPOINT_URL: process.env.ENDPOINT_URL,
    },
}

デプロイしていく

$ sls deploy --stage prd --aws-profile sample

呼び出してみる

$ sls invoke -f hello --stage prd --aws-profile sample --log

リファレンス