通过使用Apollo和Prisma来入门GraphQL

这篇文章是Hamee Advent Calendar 2019第21天的文章。

最近,我在实现某个管理界面之后意识到应该使用GraphQL进行实现。这让我重新对GraphQL产生了兴趣,因此我决定尝试一下。于是,我真的去实践了一下。

本来我计划创建一个简单的Todo应用,但最终只整理了一下GraphQL服务器的接触感觉。

t-yng/graphql-todo-app的中文释义是什么呢?

只需要一个答案:
t-yng/graphql-todo-app 是一个基于GraphQL的待办事项应用。

图书馆介绍

apollographql/apollo-server: GraphQLサーバー

実際にGraphQLのリクエストを受け付けてデータを返すサーバー
この記事ではラップされた apollo-server-express を利用していきます。

prisma/prisma: ORM

GraphQLサーバーとデータベースを繋ぐORM
prisma自体はライブラリではなくCLIツール

项目的建立

$ yarn init -y
$ yarn add typescript
$ yarn add -D ts-node
$ yarn tsc --init

安装 Apollo Server

$ yarn add apollo-server-express express @types/express

你好,世界。

实施

$ mkdir src
$ touch src/index.ts

import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';

const typeDefs = gql`
    type Query {
        hello: String
    }
`;

const resolvers = {
    Query: {
        hello: () => 'Hello GrapQL!',
    },
};

const server = new ApolloServer( { typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () => {
    console.log(`? Server ready at http://localhost:4000${server.graphqlPath}`);
});

执行

在package.json中添加执行脚本并执行。

  "scripts": {
    "dev": "ts-node src/index.ts"
  }
$ yarn dev

如果访问 http://localhost:4000/graphql 并执行以下查询,将返回结果。

query {
  hello
}

尝试实现GraphQL接口

然后,我将尝试自己实现一个获取简单To-do的GraphQL查询。

定义模式

我会写下Todo对象的类型定义以及获取它的查询的类型定义。
※ 不确定”类型定义”这个说法是否正确。

const typeDefs = gql`
    type Todo {
        id: Int!
        title: String
    }

    type Query {
        todo(id: Int): Todo
        todoes(title: String): [Todo]
    }
`;

实现解析器

解析器是接收请求并根据架构定义返回数据的部分。假设我们定义一个变量并返回值。

const todoes = [
    {
        id: 1,
        title: '簡単なタスク',
    },
    {
        id: 2,
        title: '気持ち簡単なタスク',
    },
    {
        id: 3,
        title: 'ヤバいタスク',
    },
    {
        id: 4,
        title: '気づかないフリをしているタスク',
    }
]

const resolvers = {
    Query: {
        todo: (_: any, { id }: { id: number}) => {
            return todoes.find(todo => todo.id === id);
        },
        todoes: (_: any, { title }: { title: string }) => {
            return todoes.filter(todo => todo.title.includes(title))
        },
    },
};

进行

那么,让我们启动服务器并确认到目前为止的更改。

$ yarn dev
ts-node src/index.ts
? Server ready at http://localhost:4000/graphql

哦!结果顺利获取了(*゚▽゚ノノ゙☆パチパチ
这真是一次令人兴奋、非常有趣的新鲜体验!

スクリーンショット 2019-12-21 1.56.07.png

与DB进行合作

只要去获取变量的数据是很无聊的,所以最好实践性地获取存在于数据库中的数据。

引入Prisma

Prisma是一种连接GraphQL服务器和数据库的ORM。只需编写模型定义,它会自动生成用于进行表迁移和访问数据库的代码。

感觉使用TypeORM等库和没有使用没有太大区别,完全没有享受到任何优势…

Prisma并非一个库,而是一个CLI工具,所以需要将其作为开发包进行安装。

$ yarn add -D prisma

数据库和Prisma的安装设置

只要在本地开发的话,执行init命令就能帮你搭建好数据库的Docker环境。

这次的设置是选择了创建新数据库、MySQL和Prisma TypeScript Client。

$ yarn prisma init .
yarn run v1.19.1
$ /Users/tomohiro/workspace/graphql/graphql-todo-app/node_modules/.bin/prisma init .
? Set up a new Prisma server or deploy to an existing server? Create new database
? What kind of database do you want to deploy to? MySQL
? Select the programming language for the generated Prisma client Prisma TypeScript Client

Created 3 new files:

  prisma.yml           Prisma service definition
  datamodel.prisma    GraphQL SDL-based datamodel (foundation for database)
  docker-compose.yml   Docker configuration file

Next steps:

  1. Open folder: cd .
  2. Start your Prisma server: docker-compose up -d
  3. Deploy your Prisma service: prisma deploy
  4. Read more about Prisma server:
     http://bit.ly/prisma-server-overview

Generating schema... 17ms
Saving Prisma Client (TypeScript) at /Users/tomohiro/workspace/graphql/graphql-todo-app/generated/prisma-client/

启动Docker

为了启动Prisma服务器,从自动生成的 docker-compose.yml 文件中启动Docker容器。

默认情况下,数据库容器的端口未绑定到主机端口上,因此请将以下部分注释掉以访问数据库。

# Uncomment the next two lines to connect to your your database from outside the Docker environment, e.g. using a database GUI like Workbench
ports:
- "3306:3306"
$ docker-compose up -d

数据模型的定义

我們將定義數據模型。Prisma將根據這個模型定義進行數據庫表的遷移。

type Todo {
  id: Int! @id(strategy: AUTO)
  title: String!
}

执行迁移

在执行迁移之前,请修改 prisma.yml 文件,并进行客户端代码生成路径和 hooks 的设置。

generate:
  - generator: typescript-client
    output: ./src/generated/prisma-client/ # 生成先を src/ に変更

# マイグレーション実行のタイミングでクライアントコードの生成も実行する
hooks:
  post-deploy:
    - prisma generate

现在开始执行迁移操作。

$ yarn prisma deploy
yarn run v1.19.1
$ /Users/tomohiro/workspace/graphql/graphql-todo-app/node_modules/.bin/prisma deploy
Creating stage default for service default ✔
Deploying service `default` to stage `default` to server `local` 1.3s

Changes:

  Todo (Type)
  + Created type `Todo`
  + Created field `id` of type `Int!`
  + Created field `title` of type `String!`

...

如果实际连接到MySQL,你可以确认生成了default@default/Todo数据库和选项。

host: 127.0.0.1
user: root
password: prisma

我会添加一些数据作为测试用。

INSERT INTO Todo(title) values 
    ('簡単なタスク'),
    ('気持ち簡単なタスク'),
    ('ヤバいタスク'),
    ('気づかないフリをしているタスク');

将返回DB数据的实现更改。

由于客户端库被自动生成在 src/generated/prisma-client 中,因此我们将使用它来访问数据库。

import { prisma } from './generated/prisma-client'

...

const resolvers = {
    Query: {
        todo: (_: any, { id }: { id: number}) => {
            return prisma.todo({ id });
        },
        todoes: (_: any, { title }: { title: string }) => {
            return prisma.todoes({ where: { title_contains: title }});
        },
    },
};
スクリーンショット 2019-12-21 1.56.07.png

将模式定义剥离到另一个文件中

由于使用模板字面量编写架构定义会很难阅读且维护性差,因此将其拆分到另一个文件中。

安装 graphql-import

Urigo/graphql-import 是一个能够实现导入GraphQL模式定义文件的库。

$ yarn add graphql-import

将模式定义拆分为多个部分。

我会创建一个新目录来存放模式定义。

$ mkdir src/schema
type Todo {
    id: Int!
    title: String
}
# import Todo from "todoes.graphql"

type Query {
    todo(id: Int): Todo
    todoes(title: String): [Todo]
}

导入架构定义

我感到非常清爽。

import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import { prisma } from './generated/prisma-client';
import { importSchema } from 'graphql-import';

const typeDefs = importSchema(`${__dirname}/schema/schema.graphql`);

const resolvers = {
    Query: {
        todo: (_: any, { id }: { id: number}) => {
            return prisma.todo({ id });
        },
        todoes: (_: any, { title }: { title: string }) => {
            return prisma.todoes({ where: { title_contains: title }});
        },
    },
};

const server = new ApolloServer( { typeDefs, resolvers });

结束

对于初次接触来说,整体实现似乎与 REST API 意外地相似。
不过,一旦开始全面地实施,可能会出现各种重大差异。

我很高兴终于理解了GraphQL服务器实现的概要,之前一直对此感到困惑。

如果使用prisma-labs/graphqlgen库,可以从模式定义自动生成解析器?因此,期望可以通过正确使用prisma来实现从模式定义为基础的新开发体验,所以想要尝试更多。

广告
将在 10 秒后关闭
bannerAds