GraphQLスキーマから作ったモックサーバーをSSTでデプロイする

## :sunny: はじめに

GraphQL API の開発初期に、モックサーバーがあるとクライアント側の開発が楽になります。[Apollo Server はモック機能](https://www.apollographql.com/docs/apollo-server/testing/mocking) を提供してくれていますが、デプロイするには少々手間がかかり、もっと簡単に作れないかと思っていました。
そこで最近 [SST](https://sst.dev/) というサーバーを爆速で構築するフレームワークを知り、GraphQLモックサーバーをいい感じに作れそうだったので紹介します。

SSTの詳しい説明は https://sst.dev/ を参照ください。
一言で言うとワンコマンドでサーバーをデプロイできてしまうフレームワークです。最大の特徴は [Live](https://sst.dev/docs/live/) 機能で、ローカルの変更を即座に AWS Lambda へ反映し動作確認ができます。AWS Lambda にデプロイするコードはローカルで動作確認しにくいことがあるので、これが大変魅力的でした。

## :dart: 構成

- SST を用いて AWS 上に GraphQL モックサーバーをデプロイする

- Apollo Server を AWS Lambda にホスティングする

- GraphQL スキーマファイル（`schema.graphql`）に基づいたレスポンスを Apollo Server の Mock 機能で自動生成する

## :pick: 使用技術
- SST 3.17.2

- Apollo Server v4

- TypeScript

## :hand_splayed: 手順

### SST の立ち上げ

まずは プロジェクトを構築するところから始めましょう。

```shell
mkdir graphql-lambda && cd graphql-lambda
npm init -y
```

SST を初期化します。Template は js、`Where do you want to deploy your app? You can change this later:` と聞かれるので、AWSを選択します。

```shell
npx sst@latest init


   ███████╗███████╗████████╗
   ██╔════╝██╔════╝╚══██╔══╝
   ███████╗███████╗   ██║   
   ╚════██║╚════██║   ██║   
   ███████║███████║   ██║   
   ╚══════╝╚══════╝   ╚═╝   

>  JS project detected. This will...
   - use the JS template
   - create an sst.config.ts

✓  Template: js

✓  Using: aws

✓  Done 🎉
```

ここで SSTの設定を記述する `sst.config.ts` が作られます。`async run()` が空なので記述します。

```diff_tsx
/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: "aws-express",
      removal: input?.stage === "production" ? "retain" : "remove",
      protect: ["production"].includes(input?.stage),
      home: "aws",
    };
  },
-  async run() {},
+   async run() {
+   new sst.aws.Function("GraphQLMock", {
+     url: true,
+     handler: "src/index.graphqlHandler",
+    });
+  },
+ }
});
```

SST は Terraform のように、作成するAWSリソースをコードで記述します。ここでは [Function](https://sst.dev/docs/component/aws/function/) を使い、これにより後のコマンドで AWS Lambda のリソースが作られます。

次に `handler` を記述します。
Apollo には、Lambda にデプロイする専用のhandlerが提供されているのでこれを使いましょう。

https://www.apollographql.com/docs/apollo-server/deployment/lambda

ドキュメントに従い、必要なパッケージをインストールして、`index.ts` を作成します。

```shell
npm install @apollo/server graphql @as-integrations/aws-lambda

npm install -D typescript

mkdir src && touch src/index.ts
```

とりあえず動くことを確認したいので、スキーマはあとで読み込むとして、今はハードコーディングします。

```index.ts
import { ApolloServer } from '@apollo/server';
import {
  startServerAndCreateLambdaHandler,
  handlers,
} from '@as-integrations/aws-lambda';

// TODO: あとでスキーマファイルから読み込む
const typeDefs = `#graphql
  type Query {
    hello: String
  }
`;

const resolvers = {
  Query: {
    hello: () => 'world',
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// NOTE: sst.config.ts の handler に記述した名前と一致させる
export const graphqlHandler = startServerAndCreateLambdaHandler(
  server,
  handlers.createAPIGatewayProxyEventV2RequestHandler()
);
```

### AWS 認証情報の設定

AWSリソースを作成するので、AWSアカウントの認証情報が必要です。

いくつか設定の方法はありますが、今回はprofileを読み込む方法をとってみます。他の方法は以下を参照ください。

https://sst.dev/docs/iam-credentials#credentials

IAM を作成し、アクセスキーIDとシークレットアクセスキーを `~/.aws/credentials` に保存します。IAM は`AdministratorAccess` があるものにします（本当は最低限がいいですが）。この辺りのやり方は調べたら出てくると思いますのでこの記事では割愛します。

```
[default]
aws_access_key_id = <DEFAULT_ACCESS_KEY_ID>
aws_secret_access_key = <DEFAULT_SECRET_ACCESS_KEY>

[miyamasaru]
aws_access_key_id = <MY_ACCESS_KEY_ID>
aws_secret_access_key = <MY_SECRET_ACCESS_KEY>

```

profileを `sst.config.ts` に追加します。profile名は `~/.aws/credentials` に設定したものと一致させます。

```diff_tsx
export default $config({
  app(input) {
    return {
      name: "aws-express",
      removal: input?.stage === "production" ? "retain" : "remove",
      protect: ["production"].includes(input?.stage),
      home: "aws",
+     providers: {
+       aws: {
+         profile: "miyamasaru"
+       }
+     }
    };
  },

```

これで準備完了です！ `sst dev` を実行します。

```shell
sst dev
```

:::note warn
SSTのドキュメントでは `npx sst dev` が実行されていますが、Deployが終わらずうまく実行できなかったので、[sstのCLI](https://sst.dev/docs/reference/cli/#_top) をインストールして `sst dev` で実行したら安定しました。

2025年6月現在、似たようなissueが上がっているようです。
https://github.com/sst/sst/issues/5655
:::

ターミナルに専用のウィンドウが出て、Complete が出れば成功です！

![スクリーンショット 2025-06-05 19.37.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/584114/da136bef-d488-411d-a88e-99227cf8a8f8.png)

Permallink の URL はアクセスするとエラーとなりますが、そちらではなく、その下の Lambda の URL にブラウザからアクセスしましょう。Apollo Server の Sandbox が確認できるはずです。

もちろん GraphQLのリクエストもできます。 `/graphql` をつけるのを忘れないようにしましょう。

```shell
$ curl -X POST https://ulut4yniqrmfbyd5ykjpydwvpe0rdaky.lambda-url.ap-northeast-1.on.aws/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "query ExampleQuery { hello }"}'
{"data":{"hello":"world"}}                
```

`index.ts` の 「world」 の部分を書き換えて再度リクエストすると、リアルタイムで変更されるかと思います。すごいですね。

### AWS Lambda へのデプロイ

`sst dev` はあくまで開発用に Lambda を立ち上げるコマンドなので、Curl + C で停止すると Lambda もアクセスできなくなってしまいます。

そこで、[deploy](https://sst.dev/docs/reference/cli/#deploy) コマンドで実際にデプロイをします。`--stage` でのデプロイ先の環境を指定することもできます。

```
sst deploy --stage production
```

ここで生成されるURLを共有することでみんながモックを使用できるようになります。

### GraphQL スキーマファイルの読み込み

では、後回しにしていた `.graphql` ファイルからモックを作成してみましょう。ルートに `schema.graphql` を作成します。

```schema.graphql
type User {
    id: ID!
    name: String!
    email: String!
    age: Int!
    posts: [Post!]!
}

type Post {
    id: ID!
    body: String!
    createdBy: User!
    comments: [Comment]!
}

type Comment {
    id: ID!
    body: String!
    createdBy: User!
}

type Query {
  # 全ての投稿を取得
  posts: [Post]!
}

```

ファイルを Lambda に渡すには、[copyFiles](https://sst.dev/docs/component/aws/function/#copyfiles) を追加します。`sst.config.ts` を以下のように変更します。

```diff_tsx
  async run() {
    new sst.aws.Function("GraphQLMock", {
      url: true,
      handler: "src/index.graphqlHandler",
+     copyFiles: [{ from: "schema.graphql" }]
    });
  },
```

そして `index.ts` でファイルを読ませます。

```index.tsx
import { ApolloServer } from '@apollo/server';
import {
  startServerAndCreateLambdaHandler,
  handlers,
} from '@as-integrations/aws-lambda';
import { readFileSync } from 'fs';

// 修正
const typeDefs = readFileSync(
  'schema.graphql',
  'utf-8'
);

// hello()はなくなりエラーになるので消す
const resolvers = {
  Query: {
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers
});

export const graphqlHandler = startServerAndCreateLambdaHandler(
  server,
  handlers.createAPIGatewayProxyEventV2RequestHandler()
);

```

### Apollo の Mock を導入する

今のままだと型エラーでレスポンスが取得できません。Apollo の Mock を導入して値が返ってくるようにしましょう。

https://www.apollographql.com/docs/apollo-server/testing/mocking

まず必要なライブラリをインストールします。

```shell
npm install --save-dev @graphql-tools/mock @graphql-tools/schema
```

`mocks` を定義して ApolloServer に渡します。
`mocks` に型と値の対応を記述することで、その型であれば自動的にその値を返すようにすることができます。

```index.ts
import { addMocksToSchema } from '@graphql-tools/mock';
import { makeExecutableSchema } from '@graphql-tools/schema';

...

// 追加
const mocks = {
  Int: () => 6,
  String: () => 'Hello',
};

// mocksを渡すように修正
const server = new ApolloServer({
  schema: addMocksToSchema({
    schema: makeExecutableSchema({ typeDefs, resolvers }),
    mocks,
  }),
});

...
```

`sst dev` でサーバーを立ち上げ、リクエストすると値が帰ってくるはずです。デプロイしたい場合は `sst deploy` を実行しましょう！

```graphql
query ExampleQuery {
  posts {
    id
    created_by {
      id
      age
      email
    }
  }
}

```

```json
{
  "data": {
    "posts": [
      {
        "id": "f8e888f6-f6f3-44e5-9c4d-c990c0aa591c",
        "createdBy": {
          "id": "5b08d7a8-c579-4ac6-ae9f-06219e615fbc",
          "email": "Hello",
          "age": 6
        }
      },
      {
        "id": "5cf0a6dc-3df8-44a8-8759-361ad31e25bb",
        "createdBy": {
          "id": "97c0463b-2fd2-4469-8e63-64d8c39034cc",
          "email": "Hello",
          "age": 6
        }
      }
    ]
  }
}

```

## 感想

SST を使えば1日足らずで Lambda の環境を構築できるのは便利でした。開発専用のLambdaをお手軽に立てられるので、ローカルでのデバッグがしやすいのも良かったです。（Serverless Framework だと、GraphQL の Query を文字列としてbodyに渡すのが面倒そう？）

https://www.apollographql.com/docs/apollo-server/deployment/lambda#running-locally