【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. 参考

投稿者プロフィール

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

関連記事

  1. GraphQL

    【GraphQL】Apollo ServerやPrisma・MySQL…

  2. GraphQL

    【GraphQL】Apollo ServerやTypeScriptを使…

最近の記事

  1. Node.js
  2. AWS
  3. AWS

制作実績一覧

  1. Checkeys