GraphQL + Node.js -データベースとORM編-

ここからはデータベースとORMについてさわっていきます。使用するデータベースはPostgresQL、ORMはPrismaです。

 そもそもORMとは

オブジェクト関係マッピング(英: Object-relational mapping、O/RM、ORM)とは、 データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。 オブジェクト関連マッピングとも呼ぶ。実際には、オブジェクト指向言語から使える「仮想」オブジェクトデータベースを構築する手法である。 オブジェクト関係マッピングを行うソフトウェアパッケージは商用のものもフリーなものもあるが、場合によっては独自に開発することもある。 (Wikipeida)

要するにデータベースとオブジェクト指向言語をつなぐものです。

 Prismaについて

 
Prismaは、GraphQLリゾルバー内でデータベースにクエリを実行するために使用されるORMです。GraphQLエコシステムのお気に入りのツールやライブラリすべてと完全に連携します。SDLファーストおよびコードファーストのGraphQLスキーマ、およびApollo Server、Express、NestJS、Mercuriusなどの任意のサーバーライブラリで使用できます。(Prisma公式ドキュメントより)

また公式ページには”なぜPrismaとGraphQLなのか?”が記されているので気になる人は是非(公式:Prismaについて

ここから新しくアプリケーションを作り上げるのですが予めPostgresQLのインストールと起動を予めしておいてください。

PostgresQLのインストール(共にQiitaから)

Windowsユーザー:https://qiita.com/tom-sato/items/037b8f8cb4b326710f71

Macユーザー:https://qiita.com/yorokobi_kannsya/items/f77d074e382a88dae971

新しいプロジェクトを作成するのでターミナルで例のように

 

mkdir todo-prisma

cd todo-prisma

npm init -y

 
 

次にPrismaのCLIをインストールし、その後初期化します。

 

npm install @prisma/cli --save-dev

npx prisma init

 

prismaのディレクトリ内にprisma.schemaそれと.envファイルができているはずです。

まずprisma/prisma.schemaを見ていきましょう。


    // prisma/prisma.schema

    datasource db {
     provider = "postgresql"
     url      = env("DATABASE_URL")
   }
   
   generator client {
     provider = "prisma-client-js"
   }
              

上からdatasourceはデータベースについても情報を書いていくところです。おそらくデフォルトでpostgresqlになっているはずなので変える必要はありません。その下のurlは.envファイルにDATABASE_URLが定義されているので以下のように自身で変更してください。

postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA

HOSTやPORTはすでに決まっています。PostgresQLであればlocalhost:5432です。

DATABASE_URL="postgresql://hondaao:postgres@localhost:5432/prisma”

筆者の場合は上のようになります。データベース名はprisma です。schemaは定義しなくても問題ありません(自動でpublicという名前のスキーマができます。)

次にデータモデルをかいていきます。今回は簡単なTodoアプリを作るのでこんな感じで


    // prisma/prisma.schema

    datasource db {
      provider = "postgresql"
      url      = env("DATABASE_URL")
    }
    
    generator client {
      provider = "prisma-client-js"
    }
    
    model Todo {
      id        Int      @default(autoincrement()) @id
      createdAt DateTime @default(now())
      updatedAt DateTime @updatedAt
      content   String
      isDone    Boolean  @default(false)
    }
              

最終的にprisma/prisma.schemaは上のようになります。autoincrement()で自動的に増加するidを生成してくれます。

変更したら次のコマンドでマイグレーションしましょう。

npx prisma migrate dev --preview-feature --name init

npx prisma generate

prisma/migration下にmigration.sqlが作られているはずです。

また、以下のコマンドでデータベースが作られているか確認しましょう。

psql -l

次にsrc/index.jsを変更していきます。先ほどと同じようにexpressapollo-server-expressをインストールしておいてください。


    //  src/index.js

    const { PrismaClient } = require('@prisma/client')
    const prisma = new PrismaClient()
    const express = require('express');
    const { ApolloServer, gql } = require('apollo-server-express')
    
    const typeDefs = gql'
     type Query {  
        listing: [Todo!]!
        getTodo(id: Int!): Todo!
    }
    
    type Mutation {
        addTodo(content: String!): Todo!
    }
    type Todo {
        id: ID!
        content: String!
        createdAt: String!
        updatedAt: String
        isDone: Boolean!
    }
    '
    
    const resolvers =  {
        Query: {
            listing: (_,__,context) => {
                return context.prisma.todo.findMany()
            },
            getTodo(_, { id }, context) => {
                return context.prisma.todo.findUnique({ where: {id} })
            }
        },
        Mutation: {
            addTodo: async(_, { content }, context ) => {
                const newTodo = await context.prisma.todo.create({
                    data: {
                        content
                    }
                })
                return newTodo
            }
        }  
    }
    
    const server = new ApolloServer({
        typeDefs,
        resolvers,
        context: {
            prisma
        }
    })
      
    const app = express();
    server.applyMiddleware({ app });
    
    app.listen({ port: 4000 }, () =>
      console.log('🚀 Server ready at http://localhost:4000/graphql')
    )
              

typeDefsresolversについては、これまででさわってきたのでわかると思います。今回新しくcontextとその中にprismaが追加されているのが わかると思います。これによりcontext.prismaに全てのresolver関数でアクセスできます。実際にresolver関数でcontext.prisma.todoによってデータベースにアクセスしているのがわかると思います。

context.prisma.todo.findManyで複数のTodoを context.prisma.todo.findUnique({ where: {id} }) で一致するidのTodoをひとつ取り出すという意味になります。

 

node src/index.jsでサーバーを起動してPlaygroundにとんでください。さっそく新しくTodoを追加しましょう。


 mutation {
   addTodo(content: "Learing GraphQL") {
     content
     isDone
   }
 }
                    

以下のようになっていればOKです。またQueryのlistingも試してみてください。先ほど作ったデータがかえってくるはずです。

 

続いて、Prisma Studioを見ていきます。これは自分がつくったデータベース上のデータをわかりやすく可視化してくれているので便利です。次のコマンドでブラウザのhttp://localhost:5555に移動するはずです。

npx prisma studio

ページが開いたら既につくったテーブルのTodoがあるはずなので中に入ると下のようになります。

 

idが1の先ほどつくったデータがあるはずです。また、エラーなどでうまく動かなかった人はGithubのコードをおいておくので確認してみてください。またPostgresQLでエラーがでている人などは権限やパスワードの確認をしてみてください。

Github:Todo App Tutorial

最後にCRUDでいうUPDATEとDELETE機能を追加します。先に進みたい方はスキップしていただいても構いません。Todoの作成のようにまずはtypeDefsのtype MutationupdateTododeleteTodoを追加しましょう。


  type Mutation {
    addTodo(content: String!): Todo
    updateTodo(id: Int!, content: String!): Todo
    deleteTodo(id: Int!): String
}
                    

アップデートはidとcontentを引数にとります。まずidを検索してcontentで内容を変更します。デリート機能はidで登録したTodoを検索し削除します。deleteTodoでは削除後に削除完了したことを伝えるメッセージを送信するのでString型を返します。

続いてresolverも記述していきます。先ほどのaddTodoに加えtype Mutationに定義したupdateTodo, deleteTodoを書き足していきます。


    Mutation: {
      addTodo: async(_, { content }, context ) => {
          const newTodo = await context.prisma.todo.create({
              data: {
                content
              }
          })
          return newTodo
      },
      updateTodo: async(_, { id, content }, context ) => {
          const updateTodo = await context.prisma.todo.update({
              where: {
                id
              },
              data: {
                content
              }
          })
          return updateTodo
      },
      deleteTodo: async(_, { id }, context ) => {
        try {
           await context.prisma.todo.delete({ where: { id }})
           return "todo was deleted!"   
        } catch (error) {
           console.log(error)
           return "Failed"
        }
      }
                    

次は今作ったAppにログイン機能を付け足します。