1. 概要
前回はS3+Cloudfrontにウェブコンテンツをデプロイし、サブドメインにアクセスする内容でした。今回はSAMテンプレートを使い、GraphQL APIを開発する内容となります。
2. Nodeのインストール
こちらを参考
3. AWSアカウントにサインアップ
3-1. 前提条件
4. AWSアクセスキーの取得
4-1. AWSアクセスキーの取得
5. AWS CLI のインストール
5-1. インストール
6. AWS SAM CLIのインストール
6-1. インストール
7. アプリケーションを初期化
7-1. init
sam init
- Which template source would you like to use?
- AWS Quick Start Templates
- Choose an AWS Quick Start application template
- 10 – GraphQLApi Hello World Example
- Project name
- sam-graphql-api
8. ソースコード
ディレクトリ構成
.
├── gql
│ ├── createPostItem.js
│ ├── deletePostItem.js
│ ├── getPostFromTable.js
│ ├── greet.js
│ ├── listPostsFromTable.js
│ ├── preprocessPostItem.js
│ ├── schema.graphql
│ └── updatePostItem.js
├── greeter
│ ├── app.mjs
│ ├── package.json
│ └── tests
│ ├── events
│ │ └── appsync.json
│ └── unit
│ └── test-handler.mjs
├── samconfig.toml
└── template.yaml
5 directories, 14 files
※変更のあるもののみを記載
8-1. gql/preprocessPostItem.js
import { util } from "@aws-appsync/utils";
export const request = (ctx) => {
const id = util.autoId();
const now = util.time.nowISO8601();
const item = {
id,
author: ctx.args.author,
title: ctx.args.title,
content: ctx.args.content,
ups: 1,
downs: 0,
version: 1,
gsi1pk: ctx.args.category,
gsi1sk: now,
};
return { payload: { id, item } };
};
export const response = (ctx) => {
return ctx.result;
};
8-2. gql/createPostItem.js
import * as ddb from "@aws-appsync/utils/dynamodb";
export const request = (ctx) => {
const { id, item } = ctx.prev.result;
return ddb.put({ key: { id }, item });
};
export const response = (ctx) => ctx.result;
8-3. gql/updatePostItem.js
※新規
import * as ddb from "@aws-appsync/utils/dynamodb";
export const request = (ctx) => {
const { id, ...rest } = ctx.args;
const values = Object.entries(rest).reduce((obj, [key, value]) => {
obj[key] = value ?? ddb.operations.remove();
return obj;
}, {});
return ddb.update({
key: { id },
update: { ...values, version: ddb.operations.increment(1) },
});
};
export const response = (ctx) => ctx.result;
8-4. gql/listPostsFromTable.js
※新規
import * as ddb from "@aws-appsync/utils/dynamodb";
export const request = (ctx) => {
const { limit = 20, nextToken, category } = ctx.args;
return ddb.query({
index: "GSI1",
query: {
gsi1pk: { eq: category },
},
limit,
nextToken: nextToken || undefined,
});
};
export const response = (ctx) => {
const { items = [], nextToken } = ctx.result;
return { posts: items, nextToken };
};
8-5. gql/getPostFromTable.js
import * as ddb from "@aws-appsync/utils/dynamodb";
export const request = (ctx) => {
return ddb.get({ key: { id: ctx.args.id } });
};
export const response = (ctx) => ctx.result;
8-6. gql/deletePostItem.js
※新規
import * as ddb from "@aws-appsync/utils/dynamodb";
export const request = (ctx) => {
const id = ctx.args.id;
return ddb.remove({ key: { id } });
};
export const response = (ctx) => ctx.result;
8-7. gql/schema.graphql
schema {
query: Query
mutation: Mutation
}
type Query {
sayHello(name: String): String
sayGoodbye(name: String): String
getPost(id: ID!): Post
listPosts(limit: Int, nextToken: String, category: String!): PaginatedPosts
}
type Mutation {
addPost(
category: String!
author: String!
title: String!
content: String!
): Post!
updatePost(id: ID!, author: String, title: String, content: String): Post
deletePost(id: ID!): Post
}
type Post {
id: String!
author: String
title: String
content: String
ups: Int!
downs: Int!
version: Int!
}
type PaginatedPosts {
posts: [Post!]!
nextToken: String
}
8-7. template.yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
sam-graphql-api
Sample SAM Template for sam-graphql-api
Resources:
PostsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: PostsTable
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: gsi1pk
AttributeType: S
- AttributeName: gsi1sk
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
GlobalSecondaryIndexes:
- IndexName: GSI1
KeySchema:
- AttributeName: gsi1pk
KeyType: HASH
- AttributeName: gsi1sk
KeyType: RANGE
Projection:
ProjectionType: ALL
Greeter:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: greeter/
Handler: app.lambdaHandler
Runtime: nodejs22.x
Architectures:
- x86_64
HelloWorldGraphQLApi:
Type: AWS::Serverless::GraphQLApi
Properties:
SchemaUri: ./gql/schema.graphql
Auth:
Type: API_KEY
ApiKeys:
MyApiKey:
Description: my api key
DataSources:
DynamoDb:
Posts:
TableName: !Ref PostsTable
TableArn: !GetAtt PostsTable.Arn
Lambda:
Greeter:
FunctionArn: !GetAtt Greeter.Arn
Functions:
preprocessPostItem:
Runtime:
Name: APPSYNC_JS
Version: 1.0.0
DataSource: NONE
CodeUri: ./gql/preprocessPostItem.js
createPostItem:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
DataSource: Posts
CodeUri: ./gql/createPostItem.js
updatePostItem:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
DataSource: Posts
CodeUri: ./gql/updatePostItem.js
deletePostItem:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
DataSource: Posts
CodeUri: ./gql/deletePostItem.js
getPostFromTable:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
DataSource: Posts
CodeUri: ./gql/getPostFromTable.js
listPostsFromTable:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
DataSource: Posts
CodeUri: ./gql/listPostsFromTable.js
greet:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
DataSource: Greeter
CodeUri: ./gql/greet.js
Resolvers:
Mutation:
addPost:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- preprocessPostItem
- createPostItem
updatePost:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- updatePostItem
deletePost:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- deletePostItem
Query:
getPost:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- getPostFromTable
listPosts:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- listPostsFromTable
sayHello:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- greet
sayGoodbye:
Runtime:
Name: APPSYNC_JS
Version: "1.0.0"
Pipeline:
- greet
Outputs:
HelloWorldGraphQLApi:
Description: HelloWorldGraphQLApi endpoint URL for Prod environment
Value: !GetAtt HelloWorldGraphQLApi.GraphQLUrl
HelloWorldGraphQLApiMyApiKey:
Description: API Key for HelloWorldGraphQLApi
Value: !GetAtt HelloWorldGraphQLApiMyApiKey.ApiKey
9. クラウド上にAPIデプロイし、確認
※必要に応じてログイン
aws sso login
9-1. sam sync
※ルートディレクトリ(sam-graphql-api)にて
sam sync --stack-name sam-graphql-api --watch
.
.
.
CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
--------------------------------------------------------------------------------------------------------------------------------------------------
Key HelloWorldGraphQLApi
Description HelloWorldGraphQLApi endpoint URL for Prod environment
Value https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql
Key HelloWorldGraphQLApiMyApiKey
Description API Key for HelloWorldGraphQLApi
Value da2-abcdefghijklmnopqrstuvwxyz
--------------------------------------------------------------------------------------------------------------------------------------------------
Stack creation succeeded. Sync infra completed.
Infra sync completed.
- GraphQL API Endpoint
- HelloWorldGraphQLApi
- https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql
- API Key
- da2-abcdefghijklmnopqrstuvwxyz
- HelloWorldGraphQLApi
10. APIを実行して、CRUDを確認
10-1. データを登録
curl -X POST https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: da2-abcdefghijklmnopqrstuvwxyz" \
-d '{
"query":"mutation MyMutation($category: String!, $author: String!, $title: String!, $content: String!) { addPost(category: $category, author: $author, title: $title, content: $content) { id } }",
"variables": {
"category": "POST",
"author": "Sondon",
"title": "Hello",
"content": "GraphQL API"
}
}'
{"data":{"addPost":{"id":"629b4ad7-dbb7-4aa7-b96d-50c162e8bbb9"}}}
curl -X POST https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: da2-abcdefghijklmnopqrstuvwxyz" \
-d '{
"query":"mutation MyMutation($category: String!, $author: String!, $title: String!, $content: String!) { addPost(category: $category, author: $author, title: $title, content: $content) { id } }",
"variables": {
"category": "POST",
"author": "Saeko",
"title": "Aroha",
"content": "Hawaii"
}
}'
{"data":{"addPost":{"id":"db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf"}}}
10-2. データを取得(複数件)
curl -X POST https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: da2-abcdefghijklmnopqrstuvwxyz" \
-d '{
"query":"query ListPosts($limit: Int, $nextToken: String, $category: String!) { listPosts(limit: $limit, nextToken: $nextToken, category: $category) { nextToken posts { id author title content ups downs version } } }",
"variables": {
"limit": 10,
"nextToken": "",
"category": "POST"
}
}'
{"data":{"listPosts":{"nextToken":null,"posts":[{"id":"629b4ad7-dbb7-4aa7-b96d-50c162e8bbb9","author":"Sondon","title":"Hello","content":"GraphQL API","ups":1,"downs":0,"version":1},{"id":"db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf","author":"Saeko","title":"Aroha","content":"Hawaii","ups":1,"downs":0,"version":1}]}}}
- limit: 1
- データが1件取得される
- nextTokenが取得される
- 次のデータを取得する際に指定して使用
10-3. データを更新
curl -X POST https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: da2-abcdefghijklmnopqrstuvwxyz" \
-d '{
"query": "mutation UpdatePost($id: ID!, $author: String, $title: String) { updatePost(id: $id, author: $author, title: $title) { id version author title content } }",
"variables": {
"id": "db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf",
"author": "Taylor",
"title": "Hi"
}
}'
{"data":{"updatePost":{"id":"db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf","version":2,"author":"Taylor","title":"Hi","content":"Hawaii"}}}
10-4. データを取得(1件)
curl -X POST https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: da2-abcdefghijklmnopqrstuvwxyz" \
-d '{
"query":"query GetPost($id: ID!) { getPost(id: $id) { id author title content ups downs version } }",
"variables": {
"id": "db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf"
}
}'
{"data":{"getPost":{"id":"db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf","author":"Taylor","title":"Hi","content":"Hawaii","ups":1,"downs":0,"version":2}}}
10-5. データを削除
curl -X POST https://abcdefghijklmnopqrstuvwxyz.appsync-api.ap-northeast-1.amazonaws.com/graphql \
-H "Content-Type: application/json" \
-H "x-api-key: da2-abcdefghijklmnopqrstuvwxyz" \
-d '{
"query":"mutation DeletePost($id: ID!) { deletePost(id: $id) { id author title content } }",
"variables": {
"id": "db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf"
}
}'
{"data":{"deletePost":{"id":"db8c9fa5-fe24-4ea5-85bb-b433d0e2bbdf","author":"Taylor","title":"Hi","content":"Hawaii"}}}
11. Management Consoleで確認
11-1. 画面で確認
- DynamoDB



12. AWSクラウドからアプリケーションを削除
※必要に応じ削除
sam delete --stack-name sam-graphql-api
aws cloudformation delete-stack --stack-name sam-graphql-api
13. 備考
今回はSAMテンプレートを使い、GraphQL APIを開発する内容でした。
14. 参考
- AWS Serverless Application Model (AWS SAM) とは何ですか? – AWS Serverless Application Model (amazon.com)
- とは AWS AppSync – AWS AppSync GraphQL
- AWS::Serverless::GraphQLApi – AWS Serverless Application Model
- DynamoDB を使用した設計とアーキテクチャの設計に関するベストプラクティス – Amazon DynamoDB
投稿者プロフィール

-
開発好きなシステムエンジニアです。
卓球にハマってます。