Kosaku Kurino

Kosaku Kurino

【GraphQL】Neo4jを弄くり回すGraphQLを30分で作ってみた

はじめに

MySQLなど有名なリレーショナルデータベースを対象にしたGraphQLの解説はよくみるが、グラフデータベースを対象にした記事が少ない。今回はNodejsで、Neo4jを弄くり回すGraphQLを30分で実装します。

Neo4jとGraphQLがどのようなものかは知っている前提で解説しますので、知らない方はまずググって下さい。

Neo4jはオープンソースの最も人気のあるグラフデータベースです。

条件

ローカル環境でNeo4jを動かすことができる。 GraphQL、Neo4jの使い方の基礎は知っている。

環境を整える

Neo4jにAPOCを導入

APOCはNeo4jの拡張ライブラリで、Cypherで使える表現が増えます。GraphQLからデータを更新する際に使用します。

Neo4j公式サイトに導入方法が書かれています。 APOC User Guide 3.4.0.4

ES6でJavascriptを書けるようにする

まず、neo4j-graphql-sampleフォルダを作成します。

import等、ES6のJavascriptをNodejsで実行できるコードへトランスパイルするためにbabelを導入します

詳しくはこちらの記事見てください。

$ mkdir neo4j-graphql-sample
$ cd neo4j-graphql-sample
$ npm init -y
$ npm install --save-dev babel-cli babel-preset-env

.babelrcを作成します。

{
  "presets": [
    [
      "env", {
        "targets": {
            "node": "current"
        }
      }
    ]
  ]
}

package.jsonを修正します。

{
  ...
  "scripts": {
    "start": "babel-node index.js"
  },
  ...
}

GraphQL実装に必要なライブラリをインストール

GraphQL実装には、Apolloというライブラリを使用します。 Apolloを使用すると簡単にGraphQLサーバーとフロントエンドをつなぐことができます。GraphQL実装の話はもはやApolloサーバー実装の話になっているほどメジャーなライブラリです。

また、neo4jを弄り回すので、neo4j-driverneo4j-graphql-jsを利用します。

$ npm install --save apollo-server-express \
apollo-errors \
express \
cors \
graphql \
graphql-tag \
graphql-tools \
neo4j-driver \
neo4j-graphql-js

GraphQLを作る

graphQL実装のポイントとして、クエリの型を定義したスキーマ、返すデータを作るリソルバー、Neo4jとデータのやりとりを行うドライバーを用意し、Apolloサーバーに設定します

Apolloサーバーは今回はexpressを利用して作ります。

それでは、index.jsを作成し実際にコードを書いていきます。

import { makeAugmentedSchema } from 'neo4j-graphql-js'
import { ApolloServer } from 'apollo-server-express'
import express from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
import { v1 as neo4j } from 'neo4j-driver'
import { typeDefs, resolvers } from './schema'

// スキーマの作成
// typeDefsにQueryとMutationの型を定義する
const schema = makeAugmentedSchema({
  typeDefs,
  config: {
    query: true,
    mutation: false
  }
})

const neo4jUri = process.env.NEO4J_URI || 'bolt://localhost:7687'
const neo4jUser = process.env.NEO4J_USER || 'neo4j'
const neo4jPassword = process.env.NEO4J_PASSWORD || 'neo4j'

// ドライバーの作成
const driver = neo4j.driver(neo4jUri, neo4j.auth.basic(neo4jUser, neo4jPassword))

// expressサーバーの作成
const app = express()
app.use(bodyParser.json())
app.use(cors())

const playgroundEndpoint = '/graphql'

// Apolloサーバーの作成
// playgroundは試しにクエリを流せるエディタ、後で使用する
const server = new ApolloServer({
  schema,
  resolvers,
  context: ({ req }) => {
    return {
      driver,
      req,
    }
  },
  introspection: true,
  playground: {
    endpoint: playgroundEndpoint,
    settings: {
      'editor.theme': 'light'
    }
  }
})

// expressとapolloサーバーを繋げる
server.applyMiddleware({ app, path: '/' })

app.listen(4000, () => {
  console.log(`http://localhost:4000/graphql`)
})

schema.jsを作成します。

schema.jsにはスキーマの型とリソルバーを定義します

スキーマの型では@relationと@cypherのディレクティブを使用できます。@relationはNeo4jのノード同士のエッジの繋がりを定義でき、@cypherはCypherクエリをNeo4jに直接実行させることができます。

リソルバーとは、スキーマをもとに、返すデータを作成します。 リソルバーはneo4jgraphqlを使えば、よしなにQueryかMutationかを判断しDB操作後、返すデータを作成してくれます。

今回は、ノードとして「User」と「Article」、エッジとして「WRITE」を追加、参照できるQueryとMutationの型を作ります

import { neo4jgraphql } from 'neo4j-graphql-js'

export const typeDefs = `
  type Article {
    _id: ID
    uuid: ID!
    title: String!
    description: String!
    created_at: Date!
    write_user: User @relation(name: "WRITE", direction: "IN")
  }

  type User {
    _id: ID
    uuid: ID!
    name: String!
    created_at: Date!
    write_articles: [Article] @relation(name: "WRITE", direction: "OUT")
  }

  type Query {
    Article(_id: ID, title: String, description: String, created_at: Date): [Article]
    User(_id: ID, uid: ID, name: String, avatar: String, created_at: Int): [User]
  }

  type Mutation {
    writeArticle(title: String!, description: String! user_uuid: ID!): Article
      @cypher(statement:"MATCH (u:User {uuid: $user_uuid}) MERGE (u)-[r:WRITE]->(n:Article {uuid: apoc.create.uuid(), title: $title, description: $description, created_at: apoc.date.format(apoc.date.add(timestamp(), 'ms', 9, 'h'), 'ms')}) return n")

    createUser(name: String!): User
      @cypher(statement:"CREATE (n:User {uuid: apoc.create.uuid(), name: $name, created_at: apoc.date.format(apoc.date.add(timestamp(), 'ms', 9, 'h'), 'ms')}) return n")
  }
`

export const resolvers = {
  Query: {
    Article(obj, args, ctx, info) {
      return neo4jgraphql(obj, args, ctx, info)
    },
    User(obj, args, ctx, info) {
      return neo4jgraphql(obj, args, ctx, info)
    },
  },
  Mutation: {
    writeArticle(obj, args, ctx, info) {
      return neo4jgraphql(obj, args, ctx, info)
    },
    createUser(obj, args, ctx, info) {
      return neo4jgraphql(obj, args, ctx, info)
    },
  }
}

PlaygroundでGraphQLを使ってみる

Neo4jを起動しておき、npm startで先ほど実装したApolloサーバーを動かしてみましょう。

$ npm start

> neo4j-graphql-sample@1.0.0 start ~/neo4j-graphql-sample
> babel-node index.js

http://localhost:4000/graphql

Apolloサーバーの立ち上げに成功すると、localhost:4000/graphqlにアクセスすることでPlaygroundという、GraphQLのクエリをブラウザ上で試せるエディタを開くことができます。

GraphQLでクエリの書き方がわからないかたは下記でさらっと学んでください。 GraphQLのクエリを基礎から整理してみた

createUserのMutationでUserを追加してみましょう

スクリーンショット 2019-01-26 0.20.31.png

writeArticleのMutationでArticleを追加してみましょう。 createUser実行時取得したUserのuuidをuser_uuidに入れて実行しましょう。

スクリーンショット 2019-01-26 1.11.04.png

ArticleのQueryで参照してみましょう。

スクリーンショット 2019-01-26 1.29.32.png

ちなみに、下記のようなクエリを流すとArticleを書いたUserの情報も参照することが可能です。

スクリーンショット 2019-01-26 1.31.33.png

現時点でNeo4jにはノードとして「User」、「Article」、エッジとして「WRITE」が登録されているはずです。ブラウザでNeo4jエディタにアクセスし、確認してみましょう。

スクリーンショット 2019-01-26 1.16.39.png

さいごに

「User」と「Article」を追加、参照するGraphQLを作ることができました。

サンプルコードgithubにあげておきました。

https://github.com/kousaku-maron/neo4j-graphql-sample

今回はMutationを独自で組み込みましたが、エッジの「WRITE」を自動でつける必要がないのであれば、makeAugmentedSchemaconfigmutationをtrueにすれば、独自で作らなくても勝手にMutationが使えるようになります。

ここでは解説していない認証・認可や、GraphQLを実装する際に考えるべき点をまとめた記事も書いてます。よかったらのぞいてみてください。

【GraphQL】GraphQL実装で押さえておくべき勘所