Serverless FrameworkでLambda + Apollo Serverの環境を作りたい
- 題材として、Apollo Odyssey のAPIの一部の仕様を再現する
- TypeScriptで書きたい
- スキーマは別ファイルに出したい
node -v
v18.14.0
pnpm --version
7.27.0
スキーマ
type Query {
tracksForHome: [Track!]!
}
type Track {
id: ID!
title: String!
author: Author!
thumbnail: String
length: Int
modulesCount: Int
}
type Author {
id: ID!
name: String!
photo: String
}
参考資料
- https://www.apollographql.com/docs/apollo-server/deployment/lambda/
- https://zenn.dev/eringiv3/books/a85174531fd56a/viewer/a8fab6
- https://zenn.dev/merutin/articles/6e338f140c1be5
- https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-resolvers#use-your-model-types-mappers
- https://the-guild.dev/blog/better-type-safety-for-resolvers-with-graphql-codegen
最終成果物
ハマったとことか
-
Serverless FrameworkのドキュメントのGetting Startedで、公式のテンプレートより先にコミュニティのテンプレート(Example)が案内されていて、最終的に採用した
aws-nodejs-typescript
に辿り着けなかった -
esbuildを最新化するとserverless-esbuildがこけるので、esbuildのバージョンは0.16.xにしておく必要がある(2023-02-17時点、serverless-esbuildは1.37.3
)- https://github.com/floydspace/serverless-esbuild/issues/427
- 2023-03-13追記
1.38.0
で解消したみたい
- GraphQL/Apollo Serverの勉強に使ったApollo OdysseyはApollo Server v3を使っていたので、v4へのマイグレーションガイドをちゃんと確認しないとダメだった
- v3は
DataSources
をApolloServer
のコンストラクタに渡してたけどv4はサーバ起動時にContext
を作ってこの中で初期化する
- v3は
-
graphql-code-generator init
がJSONのパースエラーでこけていて、これが解消できなかった- 詳細なログが出せなかったので原因箇所は不明(
package.json
くらいしかJSONのエラーが出そうな要素がないのでこれ?) -
codegen.ts
は生成できていたので、それ以外(package.json
の編集)は手作業で解決した
- 詳細なログが出せなかったので原因箇所は不明(
- Resolver Chainで親Resolverの型がスキーマからgraphql-codegenした型(子Resolverも解決済みの最終的な形)になっているとtype errorが出るので、mappersというオプションでResolverの処理中に扱う中間的な型を指定する必要がある
- 今回のスキーマだと
Track.author
はgetTracksForHome
のResolverの結果時点ではauthorId
なので、Track
のauthor
をauthorId
に置き換えた型が必要-
TrackModel
型を作った- べた書きするとcodegenの意味がないのでgraphql-codegenで生成した
Track
をベースにした Omit<Track, "author"> & { authorId: string }
-
ドキュメントの例に倣って
TrackModel
と命名したけどあまり適切だと思っていない-
IntermediateTrackModel
とかTrackUnderResolution
とか? -
TrackResolverResultItem
とか
-
- べた書きするとcodegenの意味がないのでgraphql-codegenで生成した
-
- 今回のスキーマだと
- 今回はシンプルなスキーマでも苦労したので、複雑なスキーマになるとResolverの型をちゃんと付けるのかなり大変なんじゃないかと思う
体験がよかったとことか
-
aws-nodejs-typescript
テンプレートを使ったらout-of-the-boxでTypeScriptが書けた- serverless-esbuildプラグインのおかげっぽい
- Serverless Frameworkそのものもserverless-offlineプラグインも特に問題なくサッと動いてくれた
- 特にデプロイの手順が簡素で体験がよかった(AppSyncの構築で使ったときも感じた)
- AppSyncに比べるとローカルで実装/検証できるぶん(実装部分はControllableなので)トラブルシュートしやすかった
- ただこれくらいシンプルなスキーマだと慣れちゃえばAppSyncの方がサッと作れると思う
検証できてないこと
- Mutation
- 認証
- Apollo Serverで実装する場合はAppSyncより細かい制御が行えるのでは
- ProxyになってるAPI Gatewayで認証がかけられるか(IAM、Cognito)
- またはLambdaとIAMで制御する?
- など
初期化
pnpm dlx serverless create -t aws-nodejs-typescript
pnpm install
ライブラリをアップデート
ncu -u
Upgrading /Users/qmotas/workspaces/odyssey-ts-serverless/package.json
[====================] 12/12 100%
@middy/core ^3.6.2 → ^4.2.4
@middy/http-json-body-parser ^3.6.2 → ^4.2.4
@types/node ^14.18.36 → ^18.13.0
esbuild ^0.14.54 → ^0.17.8
json-schema-to-ts ^1.6.5 → ^2.6.2
tsconfig-paths ^3.14.1 → ^4.1.2
Run pnpm install to install new versions.
pnpm install
動作を確認したいのでserverless-offline
を入れる
pnpm add -D serverless-offline
- plugins: ['serverless-esbuild'],
+ plugins: ['serverless-esbuild', 'serverless-offline'],
こけた
pnpm serverless offline
(node:50290) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
✘ [ERROR] Invalid option in build() call: "incremental"
/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:255:12:
255 │ throw new Error(`Invalid option ${where}: ${quote(key)}`);
╵ ^
at checkForInvalidFlags (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:255:13)
at flagsForBuildOptions (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:457:3)
at buildOrContextContinue (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:1009:9)
at buildOrContextImpl (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:993:5)
at Object.buildOrContext (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:776:5)
at /Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:2163:15
at new Promise (<anonymous>)
at Object.build (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:2162:25)
at build (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:2011:51)
at bundleMapper (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/serverless-esbuild@1.37.3_esbuild@0.17.8/node_modules/serverless-esbuild/src/bundle.ts:98:31)
at /Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/p-map@4.0.0/node_modules/p-map/index.js:57:28
Environment: darwin, node 18.14.0, framework 3.27.0 (local), plugin 6.2.3, SDK 4.3.2
Docs: docs.serverless.com
Support: forum.serverless.com
Bugs: github.com/serverless/serverless/issues
Error:
Error: Build failed with 1 error:
/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:255:12: ERROR: Invalid option in build() call: "incremental"
at failureErrorWithLog (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:1636:15)
at /Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:953:16
at responseCallbacks.<computed> (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:697:9)
at handleIncomingPacket (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:752:9)
at Socket.readFromStdout (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/esbuild@0.17.8/node_modules/esbuild/lib/main.js:673:7)
at Socket.emit (node:events:513:28)
at Socket.emit (node:domain:489:12)
at addChunk (node:internal/streams/readable:324:12)
at readableAddChunk (node:internal/streams/readable:297:9)
at Socket.Readable.push (node:internal/streams/readable:234:10)
at Pipe.onStreamRead (node:internal/stream_base_commons:190:23)
esbuildの0.17.0で入った破壊的変更によって発生しているらしい
0.16.xに落とす
pnpm add -D esbuild@0.16.x
pnpm serverless offline
(node:50632) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Starting Offline at stage dev (us-east-1)
Offline [http for lambda] listening on http://localhost:3002
Function names exposed for local invocation by aws-sdk:
* hello: odyssey-ts-serverless-dev-hello
┌─────────────────────────────────────────────────────────────────────────┐
│ │
│ POST | http://localhost:3000/dev/hello │
│ POST | http://localhost:3000/2015-03-31/functions/hello/invocations │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Server ready: http://localhost:3000 🚀
ローカルで起動できた
Apollo Serverを動かす
Apolloの関連ライブラリを追加
pnpm add @apollo/server graphql @as-integrations/aws-lambda
src/
配下のファイルを削除してserver.ts
を追加
import { ApolloServer } from '@apollo/server';
import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda';
const typeDefs = `#graphql
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => 'world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
// This final export is important!
export const graphqlHandler = startServerAndCreateLambdaHandler(
server,
// We will be using the Proxy V2 handler
handlers.createAPIGatewayProxyEventV2RequestHandler(),
);
functions: {
graphql: {
handler: 'src/server.graphqlHandler',
events: [
{
httpApi: {
path: '/',
method: 'POST',
},
},
{
httpApi: {
path: '/',
method: 'GET',
},
},
],
},
},
pnpm serverless offline
(node:51840) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Starting Offline at stage dev (us-east-1)
Offline [http for lambda] listening on http://localhost:3002
Function names exposed for local invocation by aws-sdk:
* graphql: odyssey-ts-serverless-dev-graphql
┌───────────────────────────────────────────────────────────────────────────┐
│ │
│ POST | http://localhost:3000/ │
│ POST | http://localhost:3000/2015-03-31/functions/graphql/invocations │
│ GET | http://localhost:3000/ │
│ POST | http://localhost:3000/2015-03-31/functions/graphql/invocations │
│ │
└───────────────────────────────────────────────────────────────────────────┘
Server ready: http://localhost:3000 🚀
動いた
ブラウザからアクセスしたらApollo Sandboxが使えて、クエリも確認できた
API開発の準備
eslintとprettierを入れておく
pnpm add -D eslint prettier
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}
スキーマを外部ファイル化
必要なライブラリを追加
pnpm add @graphql-tools/graphql-file-loader @graphql-tools/load @graphql-tools/schema
schema.graphql
を作成
type Query {
hello: String
}
src/server.ts
を修正
import {
handlers,
startServerAndCreateLambdaHandler,
} from "@as-integrations/aws-lambda";
import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
import { loadSchemaSync } from "@graphql-tools/load";
import { addResolversToSchema } from "@graphql-tools/schema";
import { join } from "path";
const schema = loadSchemaSync(join(__dirname, "../schema.graphql"), {
loaders: [new GraphQLFileLoader()],
});
const resolvers = {
Query: {
hello: () => "world",
},
};
const schemaWithResolvers = addResolversToSchema({ schema, resolvers });
const server = new ApolloServer({
schema: schemaWithResolvers,
});
// This final export is important!
export const graphqlHandler = startServerAndCreateLambdaHandler(
server,
// We will be using the Proxy V2 handler
handlers.createAPIGatewayProxyEventV2RequestHandler()
);
pnpm serverless offline
してブラウザでアクセスしたらこけた
GET / (λ: graphql)
✖ Unhandled exception in handler 'graphql'.
✖ Error:
Unable to find any GraphQL type definitions for the following pointers:
- /Users/qmotas/workspaces/odyssey-ts-serverless/.esbuild/.build/schema.graphql
at prepareResult (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/@graphql-tools+load@7.8.12_graphql@16.6.0/node_modules/@graphql-tools/load/esm/load-typedefs.js:88:15)
at loadTypedefsSync (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/@graphql-tools+load@7.8.12_graphql@16.6.0/node_modules/@graphql-tools/load/esm/load-typedefs.js:75:20)
at loadSchemaSync (/Users/qmotas/workspaces/odyssey-ts-serverless/node_modules/.pnpm/@graphql-tools+load@7.8.12_graphql@16.6.0/node_modules/@graphql-tools/load/esm/schema.js:24:21)
at Object.<anonymous> (/Users/qmotas/workspaces/odyssey-ts-serverless/src/server.ts:11:16)
at Module._compile (node:internal/modules/cjs/loader:1226:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)
at Module.load (node:internal/modules/cjs/loader:1089:32)
at Module._load (node:internal/modules/cjs/loader:930:12)
at Module.require (node:internal/modules/cjs/loader:1113:19)
at require (node:internal/modules/cjs/helpers:103:18)
✖
Unable to find any GraphQL type definitions for the following pointers:
- /Users/qmotas/workspaces/odyssey-ts-serverless/.esbuild/.build/schema.graphql
schema.graphql
をデプロイ対象のファイルとして読み込む設定が必要らしい
- package: { individually: true },
+ package: { individually: true, include: ["./schema.graphql"] },
これで動いた
スキーマを置き換える
サンプルとして用意されていたスキーマをこれから作りたいAPIのスキーマに置き換える
type Query {
tracksForHome: [Track!]!
}
type Track {
id: ID!
title: String!
author: Author!
thumbnail: String
length: Int
modulesCount: Int
}
type Author {
id: ID!
name: String!
photo: String
}
型定義を生成する
GraphQL Code Generatorを入れる
pnpm add -D @graphql-codegen/cli
初期化
pnpm graphql-code-generator init
Welcome to GraphQL Code Generator!
Answer few questions and we will setup everything for you.
? What type of application are you building? Backend - API or server
? Where is your schema?: (path or url) ./schema.graphql
? Pick plugins: TypeScript (required by other typescript plugins), TypeScript Resolvers (strongly typed resolve functions)
? Where to write the output: src/generated/graphql.ts
? Do you want to generate an introspection file? No
? How to name the config file? codegen.ts
? What script in package.json should run the codegen? codegen
Fetching latest versions of selected plugins...
Unexpected end of JSON input
こけた 😭
codegen.ts
は生成されている
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
overwrite: true,
schema: "./schema.graphql",
generates: {
"src/generated/graphql.ts": {
plugins: ["typescript", "typescript-resolvers"],
},
},
};
export default config;
エラーログが出ないので原因がわからないけど、package.json
の更新に失敗したとかかなという決め打ちで手動セットアップを試みる
pnpm add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
pnpm graphql-codegen --config codegen.ts
✔ Parse Configuration
✔ Generate outputs
動いた
npm scriptも設定しておく
"scripts": {
"codegen": "graphql-codegen --config codegen.ts"
},
pnpm codegen
> odyssey-ts-serverless@1.0.0 codegen /Users/qmotas/workspaces/odyssey-ts-serverless
> graphql-codegen --config codegen.ts
✔ Parse Configuration
✔ Generate outputs
GraphQL Code Generatorの設定
- 出力先ディレクトリ名を明確に区別できるようにしておく(
src/__generated__
) - 設定はデフォルトのままにしておく
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
overwrite: true,
schema: "./schema.graphql",
generates: {
"src/__generated__/graphql.ts": {
plugins: ["typescript", "typescript-resolvers"],
},
},
};
export default config;
確認
型がついた
実装
DataSource(RESTDataSource
)とResolverを実装していく
Apollo OdysseyだとResolverは1ファイルで実装していたけど、typeごと分割する
データソースの実装
pnpm add @apollo/datasource-rest
import { RESTDataSource } from "@apollo/datasource-rest";
export class TrackAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://odyssey-lift-off-rest-api.herokuapp.com/";
}
async getTracksForHome() {
return this.get("tracks");
}
async getAuthor(authorId) {
return this.get(`author/${authorId}`);
}
}
Resolverの実装
Query
import { QueryResolvers } from "./../__generated__/graphql";
export const queryResolver: QueryResolvers = {
tracksForHome: (_, __, { dataSources }) => {
return dataSources.trackAPI.getTracksForHome();
},
};
dataSources
がany
になってしまうので、Contextに型を付けたい
Contextの型を追加
import { TrackAPI } from "src/datasources/track-api";
export type Context = {
dataSources: {
trackAPI: TrackAPI;
};
};
graphql-codegenの設定を追加
const config: CodegenConfig = {
overwrite: true,
schema: "./schema.graphql",
generates: {
"src/__generated__/graphql.ts": {
plugins: ["typescript", "typescript-resolvers"],
},
},
+ config: {
+ useIndexSignature: true,
+ contextType: "src/types/context#Context",
+ },
};
型定義を生成
pnpm codegen
型がついた
Track
import { TrackResolvers } from "src/__generated__/graphql";
export const trackResolver: TrackResolvers = {
author: ({ authorId }, _, { dataSources }) => {
return dataSources.trackAPI.getAuthor(authorId);
},
};
- ここでparentは
Track
になるため、authorId
が取り出せない -
TrackAPI#getTracksForHome()
はauthorId
をもつオブジェクトを戻したい -
Query
のResolver(tracksForHome()
)はauthor
をもつオブジェクトを戻したい - チェイン先の
Track
のResolver(author()
)はauthorId
を持つオブジェクトを受け取りたい - どうすればいい?
<--Track[]-- Query.tracksForHome <--- TrackAPI#getTracksForHome(): TrackWithAuthorId[]
│
<TrackWithAuthorId>
│
└─ Track.author <--- TrackAPI#getAuthor(authorId): Author
typescript-resolvers
プラグインのmappers
というオプションでResolverが内部的に使う型を明示的に指定できる
import { Track } from "src/__generated__/graphql";
export type TrackModel = {
id: string;
length?: number;
modulesCount?: number;
thumbnail?: string;
title: string;
authorId: string;
};
const config: CodegenConfig = {
overwrite: true,
schema: "./schema.graphql",
generates: {
"src/__generated__/graphql.ts": {
plugins: ["typescript", "typescript-resolvers"],
},
},
config: {
useIndexSignature: true,
contextType: "src/types/context#Context",
+ mappers: { Track: "src/types/trackModel#TrackModel" },
},
};
pnpm codegen
ただ、これだと自分でスキーマに対応した型を実装しないといけない
生成された型を使って一部だけ変更する
import { Track } from "src/__generated__/graphql";
export type TrackModel = Omit<Track, "author"> & {
authorId: string;
};
import { TrackResolvers } from "src/__generated__/graphql";
export const trackResolver: TrackResolvers = {
author: ({ authorId }, _, { dataSources }) => {
return dataSources.trackAPI.getAuthor(authorId);
},
};
いけた
Resolverをまとめてexport
import { trackResolver } from "./track";
import { Resolvers } from "src/__generated__/graphql";
import { queryResolver } from "./query";
export const resolvers: Resolvers = {
Query: queryResolver,
Track: trackResolver,
};
サーバの実装
ここまでApollo Server v3のやり方で実装してたけど、v4でけっこう変わっていたので対応する
apollo-datasource-rest
-> @apollo/datasource-rest
に置き換える
pnpm remove apollo-datasource-rest
pnpm add @apollo/datasource-rest
データソースのimportを修正
ついでに型もつけておく
import { RESTDataSource } from "@apollo/datasource-rest";
import { TrackModel } from "src/types/trackModel";
import { Author } from "src/__generated__/graphql";
export class TrackAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://odyssey-lift-off-rest-api.herokuapp.com/";
}
async getTracksForHome(): Promise<Array<TrackModel>> {
return this.get<Array<TrackModel>>("tracks");
}
async getAuthor(authorId): Promise<Author> {
return this.get<Author>(`author/${authorId}`);
}
}
v4はデータソースの指定方法が変わって、ApolloServer
のコンストラクタではなく起動処理の引数でContext
を初期化する
import { ApolloServer } from "@apollo/server";
import {
handlers,
startServerAndCreateLambdaHandler
} from "@as-integrations/aws-lambda";
import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
import { loadSchemaSync } from "@graphql-tools/load";
import { addResolversToSchema } from "@graphql-tools/schema";
import { join } from "path";
import { TrackAPI } from "./datasources/track-api";
import { resolvers } from "./resolvers";
import { Context } from "./types/context";
const schema = loadSchemaSync(join(__dirname, "../schema.graphql"), {
loaders: [new GraphQLFileLoader()],
});
const schemaWithResolvers = addResolversToSchema({ schema, resolvers });
const server = new ApolloServer<Context>({
schema: schemaWithResolvers,
});
export const graphqlHandler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventV2RequestHandler(),
{
context: async () => {
return {
dataSources: {
trackAPI: new TrackAPI(),
},
};
},
}
);
動作確認
pnpm serverless offline
動いた 🎉
開発用のnpm scriptを追加する
動くようになったので、serverless offline
で起動しつつgraphql-codegen --watch
してスキーマの変更を反映できるようにしたい
複数コマンドの同時実行を行うため、concurrentlyを入れる
pnpm add -D concurrently
スクリプトを追加する
"
で囲ったコマンドをconcurrently
の引数に指定する
"scripts": {
+ "dev": "concurrently \"serverless offline\" \"graphql-codegen --config codegen.ts --watch\"",
"codegen": "graphql-codegen --config codegen.ts"
},
pnpm dev
するとServerlessのローカル環境が起動してschema.graphql
の監視が始まる
pnpm dev
> odyssey-ts-serverless@1.0.0 dev /Users/qmotas/workspaces/odyssey-ts-serverless
> concurrently "serverless offline" "graphql-codegen --config codegen.ts --watch"
[0] (node:69347) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.
[0]
[0] Please migrate your code to use AWS SDK for JavaScript (v3).
[0] For more information, check the migration guide at https://a.co/7PzMCcy
[0] (Use `node --trace-warnings ...` to show where the warning was created)
[1] [STARTED] Parse Configuration
[1] [SUCCESS] Parse Configuration
[1] [STARTED] Generate outputs
[1] [STARTED] Generate to src/__generated__/graphql.ts
[1] [STARTED] Load GraphQL schemas
[1] [SUCCESS] Load GraphQL schemas
[1] [STARTED] Load GraphQL documents
[1] [SUCCESS] Load GraphQL documents
[1] [STARTED] Generate
[1] [SUCCESS] Generate
[1] [SUCCESS] Generate to src/__generated__/graphql.ts
[1] [SUCCESS] Generate outputs
[1] ℹ Watching for changes...
[0]
[0] Starting Offline at stage dev (us-east-1)
[0]
[0] Offline [http for lambda] listening on http://localhost:3002
[0] Function names exposed for local invocation by aws-sdk:
[0] * graphql: odyssey-ts-serverless-dev-graphql
[0]
[0] ┌───────────────────────────────────────────────────────────────────────────┐
[0] │ │
[0] │ POST | http://localhost:3000/ │
[0] │ POST | http://localhost:3000/2015-03-31/functions/graphql/invocations │
[0] │ GET | http://localhost:3000/ │
[0] │ POST | http://localhost:3000/2015-03-31/functions/graphql/invocations │
[0] │ │
[0] └───────────────────────────────────────────────────────────────────────────┘
[0]
[0] Server ready: http://localhost:3000 🚀
AWS Lambdaにデプロイする
AWSの認証情報が未設定の場合はaws configure
またはserverless config credentials
で設定する
serverless.ts
のprovider
等の設定が初期化時のままになっているので修正
-
region
にap-northeast-1
を指定 -
runtime
のバージョンをnodejs18.x
に変更- esbuildの
target
もnode18
に変更
- esbuildの
- CORSの設定を追加
- テンプレートにあったAPI Gatewayの設定を削除
import type { AWS } from "@serverless/typescript";
const serverlessConfiguration: AWS = {
service: "odyssey-ts-serverless",
frameworkVersion: "3",
plugins: ["serverless-esbuild", "serverless-offline"],
provider: {
name: "aws",
region: "ap-northeast-1",
runtime: "nodejs18.x",
httpApi: {
cors: true,
},
environment: {
AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1",
NODE_OPTIONS: "--enable-source-maps --stack-trace-limit=1000",
},
},
// import the function via paths
functions: {
graphql: {
handler: "src/server.graphqlHandler",
events: [
{
httpApi: {
path: "/",
method: "POST",
},
},
{
httpApi: {
path: "/",
method: "GET",
},
},
],
},
},
package: { individually: true, include: ["./schema.graphql"] },
custom: {
esbuild: {
bundle: true,
minify: false,
sourcemap: true,
exclude: ["aws-sdk"],
target: "node18",
define: { "require.resolve": undefined },
platform: "node",
concurrency: 10,
},
},
};
module.exports = serverlessConfiguration;
デプロイ
pnpm serverless deploy
(node:71768) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Deploying odyssey-ts-serverless to stage dev (ap-northeast-1)
✔ Service deployed to stack odyssey-ts-serverless-dev (127s)
endpoints:
POST - https://**********.execute-api.ap-northeast-1.amazonaws.com/
GET - https://**********.execute-api.ap-northeast-1.amazonaws.com/
functions:
graphql: odyssey-ts-serverless-dev-graphql (1.7 MB)
1 deprecation found: run 'serverless doctor' for more details
Need a better logging experience than CloudWatch? Try our Dev Mode in console: run "serverless --console"
動いた 🎉
1 deprecation found
とのことなので言われた通りserverless doctor
で確認する
pnpm serverless doctor
(node:71916) NOTE: The AWS SDK for JavaScript (v2) will be put into maintenance mode in 2023.
Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
1 deprecation triggered in the last command:
Support for "package.include" and "package.exclude" will be removed in the next major release. Please use "package.patterns" instead
More info: https://serverless.com/framework/docs/deprecations/#PACKAGE_PATTERNS
serverless.ts
でスキーマファイルを指定している場所の書き方が非推奨らしいのでpatterns
で書き換える
- package: { individually: true, include: ["./schema.graphql"] },
+ package: { individually: true, patterns: ["./schema.graphql"] },
AWS Lambdaのコンソールを見てみる
API Gatewayがプロキシになって、ここへのリクエストをトリガとして関数を実行するという感じだろうか(AWS Lambda何もわかってない)
export const graphqlHandler = startServerAndCreateLambdaHandler(
server,
handlers.createAPIGatewayProxyEventV2RequestHandler(),
{
context: async () => {
return {
dataSources: {
trackAPI: new TrackAPI(),
},
};
},
}
);
のhandlers.createAPIGatewayProxyEventV2RequestHandler()
の部分に対応している
不要なパッケージを除去
テンプレートで使われていたパッケージで不要なもの(消し忘れ)があったので除去した
pnpm remove @middy/core @middy/http-json-body-parser json-schema-to-ts