【GraphQL】Apollo ServerやPrisma・MySQL・TypeScriptを使ってGraphQLの更新系APIを開発する

GraphQL

1. 概要

TypeScriptをベースにApollo ServerやPrisma・MySQLを使ってGraphQLの更新系APIを開発する内容となります。

対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。

2. 【GraphQL】Apollo ServerやTypeScriptを使ってGraphQLのAPIを開発する

こちらを参考

3. PrismaやTypeScript、MySQLを使ってデータを操作する

こちらを参考

4. nodeのインストール

こちらを参考

5. プロジェクトを作成

mkdir graphql-apollo-prisma-mysql-mutation
cd graphql-apollo-prisma-mysql-mutation

6. 必要なライブラリをインストール

npm install @apollo/server graphql ts-node
npm install --save-dev prisma typescript @types/node
npm install @graphql-tools/graphql-file-loader @graphql-tools/load @graphql-tools/schema
npm install @prisma/client

7. TypeScriptでセットアップ

npx tsc --init
npx prisma init
mkdir src
touch src/index.ts
code .

8. 設定ファイルの編集

8-1. tsconfig.json

※差し替え

{
  "compilerOptions": {
    "rootDirs": ["src"],
    "outDir": "dist",
    "lib": ["es2020"],
    "target": "es2020",
    "module": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "types": ["node"]
  }
}

8-2. package.json

※追加

{
  "type": "module",
  "scripts": {
    "compile": "tsc",
    "start": "npm run compile && node ./dist/index.js"
  },
  "dependencies": {
    "@apollo/server": "^4.7.5",
    "@graphql-tools/graphql-file-loader": "^8.0.0",
    "@graphql-tools/load": "^8.0.0",
    "@graphql-tools/schema": "^10.0.0",
    "@prisma/client": "^5.0.0",
    "graphql": "^16.7.1",
    "ts-node": "^10.9.1"
  },
  "devDependencies": {
    "@types/node": "^20.4.2",
    "prisma": "^5.0.0",
    "typescript": "^5.1.6"
  }
}

9. Databaseへ接続設定

9-1. 「.env」を修正

DATABASE_URL="mysql://user:password@localhost:3306/mydb"

9-2. 「prisma/schema.prisma」を修正

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

10. スキーマを定義

※「prisma/schema.prisma」に追加

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Post {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String   @db.VarChar(255)
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}

model Profile {
  id     Int     @id @default(autoincrement())
  bio    String?
  user   User    @relation(fields: [userId], references: [id])
  userId Int     @unique
}

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  posts   Post[]
  profile Profile?
}

11. MySQLを起動

こちらを参考

起動&パーミッション付与

docker-compose up -d
docker-compose exec mysql-server bash
$ mysql -uroot -proot mysql
mysql> GRANT ALL ON *.* TO 'user';
mysql> FLUSH PRIVILEGES;

12. スキーマを作成

npx prisma migrate dev --name init

13. スキーマ定義ファイルを作成

※「src/schema.gql」

type User {
  id: ID!
  email: String!
  name: String
  posts: [Post]
  profile: Profile!
}

type Profile {
  id: ID!
  bio: String
  userId: Int!
}

type Post {
  id: ID!
  title: String!
  content: String
  published: Boolean
  authorId: Int!
}

type Query {
  users(published: Boolean!): [User]
}

type Mutation {
  createData(name: String!, email: String!, title: String!, content: String, bio: String): User
  createPost(authorId: Int!, title: String!, content: String): Post
  upsertPost(id: Int!, authorId: Int!, title: String!, content: String): Post
  updatePost(id: Int!, content: String!, published: Boolean!): Post
  deletePost(id: Int!): Post
  updateProfile(id: Int!, bio: String!): Profile
}

14. Resolverを作成

14-1-1. src/resolvers/query.ts

const users = (parent, args, contextValue, info) => {
  return contextValue.prisma.user.findMany({
    include: {
      profile: true,
      posts: {
        where: {
          published: args.published
        }
      },
    }
  });
}

export { users };

14-2-1. src/resolvers/mutation.ts

const createData = (parent, args, contextValue, info) => {
  return contextValue.prisma.user.create({
    data: {
      name: args.name,
      email: args.email,
      posts: {
        create: { title: args.title, content: args.content },
      },
      profile: {
        create: { bio: args.bio },
      }
    },
  });
}

const createPost = (parent, args, contextValue, info) => {
  return contextValue.prisma.post.create({
    data: {
      authorId: Number(args.authorId),
      title: args.title,
      content: args.content,
    },
  });
}

const upsertPost = (parent, args, contextValue, info) => {
  return contextValue.prisma.post.upsert({
    where: {
      id: Number(args.id)
    },
    update: {
      authorId: Number(args.authorId),
      title: args.title,
      content: args.content
    },
    create: {
      authorId: Number(args.authorId),
      title: args.title,
      content: args.content,
    },
  });
}

const updatePost = (parent, args, contextValue, info) => {
  return contextValue.prisma.post.update({
    where: {
      id: Number(args.id)
    },
    data: {
      content: args.content,
      published: args.published
    },
  });
}

const deletePost = (parent, args, contextValue, info) => {
  return contextValue.prisma.post.delete({
    where: {
      id: Number(args.id)
    },
  });
}

const updateProfile = (parent, args, contextValue, info) => {
  return contextValue.prisma.profile.update({
    where: {
      id: Number(args.id)
    },
    data: {
      bio: args.bio
    },
  });
}

export { createData, createPost, updatePost, upsertPost, deletePost, updateProfile };

15. ソースコード

※src/index.ts

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { loadSchemaSync } from '@graphql-tools/load';
import { addResolversToSchema } from '@graphql-tools/schema';
import { PrismaClient } from '@prisma/client';
import { users } from './resolvers/query.js';
import { createData, createPost, updatePost, upsertPost, deletePost, updateProfile } from './resolvers/mutation.js';

const schema = loadSchemaSync('src/schema.gql', {
  loaders: [new GraphQLFileLoader()],
});

const prisma = new PrismaClient();

const resolvers = {
  Query: {
    users
  },
  Mutation: {
    createData,
    createPost,
    updatePost,
    upsertPost,
    deletePost,
    updateProfile
  },
};

const schemaWithResolvers = addResolversToSchema({ schema, resolvers });

const server = new ApolloServer({
  schema: schemaWithResolvers
});

const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => ({
    ...req,
    prisma,
  }),
  listen: { port: 4000 },
});

console.log(`🚀  Server ready at: ${url}`);

16. サーバーを起動

npm start
> start
> npm run compile && node ./dist/index.js


> compile
> tsc

🚀  Server ready at: http://localhost:4000/

17. クエリを実行

  • http://localhost:4000

17-1. データの抽出

17-1-1. Request

query Users($published: Boolean!) {
  users(published: $published) {
    id
    name
    email
    profile {
      id
      bio  
    }
    posts {
      id
      title
      content
      authorId
      published
    }
  }
}

17-1-2. Valuables

{
  "published": false
}

17-2. データを作成(3つ)

17-2-1. Request

mutation CreateData($name: String!, $email: String!, $title: String!, $bio: String) {
  createData(name: $name, email: $email, title: $title, bio: $bio) {
    id
    name
    email  
  }
}

17-2-2. Valuables

{
  "name": "Alice",
  "email": "alice@prisma.io",
  "title": "Hello World",
  "bio": "I like turtles"
}

17-2-3. Valuables

{
  "name": "John",
  "email": "john@prisma.io",
  "title": "Nice guy",
  "bio": "Reading books"
}

17-2-4. Valuables

{
  "name": "Taylor",
  "email": "taylor@prisma.io",
  "title": "Good morning",
  "bio": "Making songs"
}

17-3. Postデータを作成(2つ)

17-3-1. Request

mutation CreatePost($authorId: Int!, $title: String!, $content: String) {
  createPost(authorId: $authorId, title: $title, content: $content) {
    id
    title
    content
    authorId
    published  
  }
}

17-3-2. Valuables

{
  "authorId": 1,
  "title": "Good Afternoon",
  "content": "Lunch"
}

17-3-3. Valuables

{
  "authorId": 2,
  "title": "Too far",
  "content": "100km"
}

17-4. データがなければ登録、あれば更新(今回は登録)

17-4-1. Request

mutation UpsertPost($id: Int!, $authorId: Int!, $title: String!, $content: String) {
  upsertPost(id: $id, authorId: $authorId, title: $title, content: $content) {
    id
    title
    content
    authorId
    published  
  }
}

17-4-2. Valuables

{
  "id": 6,
  "authorId": 2,
  "title": "Drink",
  "content": "Water"
}

17-5. データの更新

17-5-1. Request

mutation UpdatePost($id: Int!, $content: String!, $published: Boolean!) {
  updatePost(id: $id, content: $content, published: $published) {
    id
    title
    content
    authorId
    published  
  }
}

17-5-2. Valuables

{
  "id": 6,
  "content": "Water, please!",
  "published": true
}

17-6. データの削除

17-6-1. Request

mutation DeletePost($id: Int!) {
  deletePost(id: $id) {
    id
    title
    content
    authorId
    published  
  }
}

17-6-2. Valuables

{
  "id": 3
}

17-7. Profileデータの更新

17-7-1. Request

mutation UpdateProfile($id: Int!, $bio: String!) {
  updateProfile(id: $id, bio: $bio) {
    id
    bio
    userId  
  }
}

17-7-2. Valuables

{
  "id": 3,
  "bio": "Dogs love cats."
}

17-8. データの抽出

17-8-1. Request

query Users($published: Boolean!) {
  users(published: $published) {
    id
    name
    email
    profile {
      id
      bio  
    }
    posts {
      id
      title
      content
      authorId
      published
    }
  }
}

17-8-2. Valuables

{
  "published": false
}

18. MySQLでデータを確認

18-1. User

mysql> SELECT * FROM User;
+----+------------------+--------+
| id | email            | name   |
+----+------------------+--------+
|  1 | alice@prisma.io  | Alice  |
|  2 | john@prisma.io   | John   |
|  3 | taylor@prisma.io | Taylor |
+----+------------------+--------+
3 rows in set (0.01 sec)

18-2. Profile

mysql> SELECT * FROM Profile;
+----+-----------------+--------+
| id | bio             | userId |
+----+-----------------+--------+
|  1 | I like turtles  |      1 |
|  2 | Reading books   |      2 |
|  3 | Dogs love cats. |      3 |
+----+-----------------+--------+
3 rows in set (0.00 sec)

18-3. Post

mysql> SELECT * FROM Post;
+----+-------------------------+-------------------------+----------------+----------------+-----------+----------+
| id | createdAt               | updatedAt               | title          | content        | published | authorId |
+----+-------------------------+-------------------------+----------------+----------------+-----------+----------+
|  1 | 2023-07-22 02:06:52.788 | 2023-07-22 02:06:52.788 | Hello World    | NULL           |         0 |        1 |
|  2 | 2023-07-22 02:07:03.704 | 2023-07-22 02:07:03.704 | Nice guy       | NULL           |         0 |        2 |
|  4 | 2023-07-22 02:07:35.773 | 2023-07-22 02:07:35.773 | Good Afternoon | Lunch          |         0 |        1 |
|  5 | 2023-07-22 02:07:43.433 | 2023-07-22 02:07:43.433 | Too far        | 100km          |         0 |        2 |
|  6 | 2023-07-22 02:07:58.859 | 2023-07-22 02:08:13.204 | Drink          | Water, please! |         1 |        2 |
+----+-------------------------+-------------------------+----------------+----------------+-----------+----------+
5 rows in set (0.00 sec)

19. ディレクトリの構造

.
├── dist
│   ├── index.d.ts
│   ├── index.js
│   └── resolvers
│       ├── mutation.d.ts
│       ├── mutation.js
│       ├── query.d.ts
│       └── query.js
├── package-lock.json
├── package.json
├── prisma
│   ├── migrations
│   │   ├── 20230717125916_init
│   │   │   └── migration.sql
│   │   └── migration_lock.toml
│   └── schema.prisma
├── src
│   ├── index.ts
│   ├── resolvers
│   │   ├── mutation.ts
│   │   └── query.ts
│   └── schema.gql
└── tsconfig.json

7 directories, 16 files

20. 備考

Prismaを使用してGraphQLの更新系APIを使いMySQLデータを操作する内容でした。

21. 参考

関連記事

  1. GraphQL

    【GraphQL】Apollo ServerやPrisma・MySQL…

  2. GraphQL

    【GraphQL】Apollo ServerやTypeScriptを使…

最近の記事

  1. AWS
  2. AWS
  3. AWS
  4. AWS
  5. AWS
  6. AWS
  7. AWS
  8. AWS

制作実績一覧

  1. Checkeys