# [Terraform]
AppSync GraphQL API + DynamoDB で CRUD する


published: 2026-07-13

AppSync を DynamoDB に繋げて CRUD しました。
Terraform のサンプルコードを載せます。


outline

- ・構成
- ・GraphQL スキーマ
- ・コード
- ・動作確認

## 構成

![arch.png](https://lab.enuesaa.dev/prototype/aws-appsync-graphql-api-dynamodb-crud/arch.png)

## GraphQL スキーマ

スキーマはこんな感じです。
Create/Read/Update/Deleteします。


```graphql
schema {
  query: Query
  mutation: Mutation
}

type Query {
  notes: [Note!]!
  note(id: ID!): Note
}

type Mutation {
  createNote(input: NoteInput!): Note
  updateNote(id: ID!, input: NoteInput!): Note
  deleteNote(id: ID!): Boolean!
}

input NoteInput {
  title: String!
  message: String!
}

type Note {
  id: ID!
  title: String!
  message: String!
}

```

## コード

### `appsync_datasource_role.tf`

```tf
resource "aws_iam_role" "appsync_datasource_dynamodb" {
  name = "${var.identifier}-appsync-datasource-dynamodb"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect = "Allow",
      Principal = {
        Service = "appsync.amazonaws.com"
      },
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy" "appsync_datasource_dynamodb_dynamodb" {
  name = "dynamodb"
  role = aws_iam_role.appsync_datasource_dynamodb.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect   = "Allow",
        Action   = "dynamodb:*"
        Resource = [aws_dynamodb_table.main.arn]
      }
    ]
  })
}

```

### `appsync_datasource.tf`

```tf
resource "aws_appsync_datasource" "dynamodb" {
  api_id           = aws_appsync_graphql_api.main.id
  name             = "dynamodb"
  type             = "AMAZON_DYNAMODB"
  description      = "dynamodb"
  service_role_arn = aws_iam_role.appsync_datasource_dynamodb.arn

  dynamodb_config {
    table_name = aws_dynamodb_table.main.name
  }
}

```

### `appsync_logging_role.tf`

```tf
resource "aws_iam_role" "appsync_logging" {
  name = "${var.identifier}-appsync-logging"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [{
      Effect = "Allow",
      Principal = {
        Service = "appsync.amazonaws.com"
      },
      Action = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy" "appsync_logging_logs" {
  name = "logs"
  role = aws_iam_role.appsync_logging.id

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents",
        ]
        Resource = "*"
      }
    ]
  })
}

```

### `appsync_resolver_notes.tf`

```tf
resource "aws_appsync_resolver" "notes" {
  api_id      = aws_appsync_graphql_api.main.id
  field       = "notes"
  type        = "Query"
  kind        = "UNIT"
  data_source = aws_appsync_datasource.dynamodb.name

  code = <<EOF
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'Scan',
  };
}

export function response(ctx) {
  return ctx.result.items;
}
EOF

  runtime {
    name            = "APPSYNC_JS"
    runtime_version = "1.0.0"
  }
}

```

### `appsync_resolver_note.tf`

```tf
resource "aws_appsync_resolver" "note" {
  api_id      = aws_appsync_graphql_api.main.id
  field       = "note"
  type        = "Query"
  kind        = "UNIT"
  data_source = aws_appsync_datasource.dynamodb.name

  code = <<EOF
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'GetItem',
    key: util.dynamodb.toMapValues({
      id: ctx.args.id,
    }),
  };
}

export function response(ctx) {
  return ctx.result;
}
EOF

  runtime {
    name            = "APPSYNC_JS"
    runtime_version = "1.0.0"
  }
}

```

### `appsync_resolver_create_note.tf`

```tf
resource "aws_appsync_resolver" "create_note" {
  api_id      = aws_appsync_graphql_api.main.id
  field       = "createNote"
  type        = "Mutation"
  kind        = "UNIT"
  data_source = aws_appsync_datasource.dynamodb.name

  // see https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/js-aws-appsync-resolver-reference-dynamodb-putitem.html
  code = <<EOF
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'PutItem',
    key: util.dynamodb.toMapValues({
      id: util.autoUlid(),
    }),
    attributeValues: util.dynamodb.toMapValues({
      title: ctx.args.input.title,
      message: ctx.args.input.message,
    }),
  };
}

export function response(ctx) {
  return ctx.result;
}
EOF

  runtime {
    name            = "APPSYNC_JS"
    runtime_version = "1.0.0"
  }
}

```

### `appsync_resolver_update_note.tf`

```tf
resource "aws_appsync_resolver" "update_note" {
  api_id      = aws_appsync_graphql_api.main.id
  field       = "updateNote"
  type        = "Mutation"
  kind        = "UNIT"
  data_source = aws_appsync_datasource.dynamodb.name

  code = <<EOF
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'UpdateItem',
    key: util.dynamodb.toMapValues({
      id: ctx.args.id,
    }),
    update: {
      expression: 'SET title = :title, message = :message',
      expressionValues: util.dynamodb.toMapValues({
        ':title': ctx.args.input.title,
        ':message': ctx.args.input.message,
      }),
    },
    condition: {
      expression: 'attribute_exists(id)',
    },
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return ctx.result;
}
EOF

  runtime {
    name            = "APPSYNC_JS"
    runtime_version = "1.0.0"
  }
}

```

### `appsync_resolver_delete_note.tf`

```tf
resource "aws_appsync_resolver" "delete_note" {
  api_id      = aws_appsync_graphql_api.main.id
  field       = "deleteNote"
  type        = "Mutation"
  kind        = "UNIT"
  data_source = aws_appsync_datasource.dynamodb.name

  code = <<EOF
import { util } from '@aws-appsync/utils';

export function request(ctx) {
  return {
    operation: 'DeleteItem',
    key: util.dynamodb.toMapValues({
      id: ctx.args.id,
    }),
  };
}

export function response(ctx) {
  if (ctx.error) {
    util.error(ctx.error.message, ctx.error.type);
  }
  return true;
}
EOF

  runtime {
    name            = "APPSYNC_JS"
    runtime_version = "1.0.0"
  }
}

```

### `appsync_schema.graphql`

```graphql
schema {
  query: Query
  mutation: Mutation
}

type Query {
  notes: [Note!]!
  note(id: ID!): Note
}

type Mutation {
  createNote(input: NoteInput!): Note
  updateNote(id: ID!, input: NoteInput!): Note
  deleteNote(id: ID!): Boolean!
}

input NoteInput {
  title: String!
  message: String!
}

type Note {
  id: ID!
  title: String!
  message: String!
}

```

### `appsync.tf`

```tf
resource "aws_appsync_graphql_api" "main" {
  name = var.identifier

  schema               = file("${path.module}/appsync_schema.graphql")
  introspection_config = "ENABLED"
  authentication_type  = "API_KEY"

  log_config {
    cloudwatch_logs_role_arn = aws_iam_role.appsync_logging.arn
    field_log_level          = "ALL"
  }
}

```

### `dynamodb_table.tf`

```tf
resource "aws_dynamodb_table" "main" {
  name         = var.identifier
  billing_mode = "PAY_PER_REQUEST"

  hash_key = "id"

  attribute {
    name = "id"
    type = "S"
  }
}

```

### `main.tf`

```tf
terraform {
  required_version = "1.15.8"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }

  # backend "s3" {
  #   bucket = ""
  #   key    = ""
  #   region = "ap-northeast-1"
  # }
}

provider "aws" {
  region = "ap-northeast-1"

  default_tags {
    tags = {
      terraform = "aws-appsync-graphql-api-dynamodb-crud"
    }
  }
}

```

### `variables.tf`

```tf
variable "identifier" {
  type = string
  default = "testapp"
}

```

### memos

AppSync GraphQL API を立てます。authentication_type は API キーにしました。コンソールで API キーを発行しリクエストに付与すると API にアクセスできます

mark: `appsync.tf:1`

- [https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/security-authz.html](https://docs.aws.amazon.com/ja_jp/appsync/latest/devguide/security-authz.html)
ロギング設定です。とりあえず全部ログに出力します

mark: `appsync.tf:8`

GraphQL のスキーマです。CRUDします
・notes
・note
・createNote
・updateNote
・deleteNote


mark: `appsync_schema.graphql:1`

createNote のリゾルバーです


mark: `appsync_resolver_create_note.tf:1`

DyanmoDB の設定です

mark: `dynamodb_table.tf:1`

これを apply します


```bash
$ terraform plan | grep will
Terraform will perform the following actions:
  # aws_appsync_datasource.dynamodb will be created
  # aws_appsync_graphql_api.main will be created
  # aws_appsync_resolver.create_note will be created
  # aws_appsync_resolver.delete_note will be created
  # aws_appsync_resolver.note will be created
  # aws_appsync_resolver.notes will be created
  # aws_appsync_resolver.update_note will be created
  # aws_dynamodb_table.main will be created
  # aws_iam_role.appsync_datasource_dynamodb will be created
  # aws_iam_role.appsync_logging will be created
  # aws_iam_role_policy.appsync_datasource_dynamodb_dynamodb will be created
  # aws_iam_role_policy.appsync_logging_logs will be created

$ terraform apply

```

**動作確認**

## APIキーの発行

では実際にGraphQLのリクエストを投げていきますが、
その前にまずAPIキーを発行します。


![create-apikey.png](https://lab.enuesaa.dev/prototype/aws-appsync-graphql-api-dynamodb-crud/create-apikey.png)

## GraphQLリクエスト

では実際にリクエストを投げていきます。
まずはデータの作成です。


```gql
mutation {
  createNote(input: {
    title: "タイトル",
    message: "テキスト"
  }) {
    id
  }
}

```

![graphql-create.png](https://lab.enuesaa.dev/prototype/aws-appsync-graphql-api-dynamodb-crud/graphql-create.png)

つづいて更新です。

![graphql-update.png](https://lab.enuesaa.dev/prototype/aws-appsync-graphql-api-dynamodb-crud/graphql-update.png)

一覧

![graphql-list.png](https://lab.enuesaa.dev/prototype/aws-appsync-graphql-api-dynamodb-crud/graphql-list.png)

詳細

![graphql-get.png](https://lab.enuesaa.dev/prototype/aws-appsync-graphql-api-dynamodb-crud/graphql-get.png)

削除

![graphql-delete.png](https://lab.enuesaa.dev/prototype/aws-appsync-graphql-api-dynamodb-crud/graphql-delete.png)

