基于redux、apollo、nestjs和graphql创建的TodoList,这里部分介绍了graphql的使用方法

关于这篇文章

这是为那些想试着将运行在GraphQL上的东西进行修改的人准备的。

使用GraphQL构建的TodoList,介绍了GraphQL的部分用法。我们使用Typescript语言。
特别是涉及到GraphQL前端和后端配合的部分。

如果是在支持yarn、nodejs和docker等环境下,我认为它可以运行。
请参考各个存储库的README.md和package.json,了解详细的执行方法、版本以及使用的库等信息。

前端

后端

介绍与代码

因为有一些地方仅用文件名进行补充,所以如果您使用VSCode,可以利用ctrl + P或cmd + P的文件名搜索来更容易理解。

前端GraphQL:片段、查询、变更

将自动生成的schema.graphql从后端拿到,然后将frontend/src/graphql目录下的文件写得漂亮一些,运行yarn run graphql-codegen,就能生成frontend/src/api.ts文件,从而可以在React组件中调用它(参考frontend/src/components/TodoComponent.tsx的第12行左右)。

如果您使用VSCode,我推荐使用Apollo GraphQL扩展功能。它可以帮助您在编写frontend/src/graphql文件时,根据schema.graphql显示错误信息。有时候,即使更新了schema.graphql,更改可能不会生效,但是您可以尝试禁用然后重新启用扩展功能,以使更改生效。

片段

如果有在其他graphql文件中被多次调用的获取模式,通过创建fragment可以减少代码量。
需要从graphqlAPI返回的数据中选择数据,本例中可以从backend/src/models/todo/todo.ts和schema.graphql的5~11行中进行选择。

fragment TodoItem on Todo {
  id
  title
  isDone
}

查询

当获取数据时,使用查询(query)语句。
要获取的对象可以是数组也可以是对象(如[Todo]或Todo),在查询(query)或变更(mutation)语句中都需要以对象形式进行描述。

需要将类似于文件的第2行和第8行中的内部todos和todo与API保持一致,但是可以将第1行和第7行中的外部todos和todo改写为getTodos和getTodo。在这种情况下,需要在React组件中将useTodosQuery更改为useGetTodosQuery。

另外,也可以将文件分割并进行记录,没有任何问题。

query todos {
  todos {
    ...TodoItem
  }
}

query todo($id: ID!) {
  todo(id: $id) {
    ...TodoItem
  }
}

以下的改变方式是可行的。

query getTodo($id: ID!) {
  todo(id: $id) {
    id
    title
    isDone
    createdAt
    updatedAt
  }
}
query getTodos {
  todos {
    id
    title
    isDone
  }
}

突变

如果要更改数据,使用mutation。除此之外与查询相同。

mutation createTodo($input: TodoInput!) {
  createTodo(input: $input) {
    ...TodoItem
  }
}

mutation updateTodo($id: ID!, $input: TodoInput!) {
  updateTodo(id: $id, input: $input) {
    ...TodoItem
  }
}

mutation deleteTodo($id: ID!) {
  deleteTodo(id: $id)
}

后端GraphQL:解析器

只要后端服务器正在运行,@Query注释和其后的方法、@Mutation注释和其后的方法将自动更新backend/src/schema.graphql文件。

frontend/src/graphql/queries/todo.graphql文件的第2行对应着以下文件的第46行的方法名todos。

import { Resolver, Query, Args, ID, Mutation } from '@nestjs/graphql'
import { BadRequestException } from '@nestjs/common'

import Todo from '../models/todo/todo'

import { TodoService } from './todo.service'
import { DBService } from '../db/db.service'
import TodoInput from '../models/todo/todo.input'

@Resolver()
export class TodoResolver {
  constructor(
    private readonly dbservice: DBService,
    private readonly service: TodoService,
  ) {}

  getClient() {
    return this.dbservice.getClient()
  }

  @Query(
    returns => Todo,
    {
      description: 'todoを取得',
    },
  )

  async todo(
    @Args({ name: 'id', type: () => ID }) id: string
  ): Promise<Todo> {
    const client = this.getClient()
    const res = await this.service.get(client, id)
    if (!res) {
      throw new BadRequestException(`Todo Not Found id: ${id}`)
    }
    return res
  }

  @Query(
    returns => [Todo],
    {
      description: 'todo一覧を取得',
    },
  )

  async todos(): Promise<Todo[]> {
    const client = this.getClient()
    const res = await this.service.scan(client)
    return res
  }

  @Mutation(
    returns => Todo,
    {
      description: 'todoを作成',
    },
  )
  async createTodo(@Args('input') input: TodoInput): Promise<Todo> {
    const client = this.getClient()
    const res = await this.service.create(client, input)
    return res
  }

  @Mutation(
    returns => Todo,
    {
      description: 'todoを更新',
    },
  )
  async updateTodo(
    @Args({ name: 'id', type: () => ID }) id: string,
    @Args('input') input: TodoInput,
  ): Promise<Todo> {
    const client = this.getClient()
    const res = await this.service.get(client, id)
    if (!res) {
      throw new BadRequestException(`Todo Not Found id: ${id}`)
    }
    return await this.service.update(client, {
      ...res,
      ...input,
    })
  }

  @Mutation(
    returns => String,
    {
      description: 'todoを削除',
    },
  )
  async deleteTodo(
    @Args({ name: 'id', type: () => ID }) id: string,
  ): Promise<string> {
    const client = this.getClient()
    const res = await this.service.get(client, id)
    if (!res) {
      throw new BadRequestException(`Todo Not Found id: ${id}`)
    }
    await this.service.delete(client, id)
    return id
  }
}

总结

这是主要的GraphQL部分。

个人认为,GraphQL API通过在代码本身明确了数据的发送和接收内容,使得处理过程更易于理解,减轻了开发负担。特别是在与API的连接和实现的变更方面,处理更易于理解,能够顺利进行。

尝试修改方法名称,增加原始功能,体验一下GraphQL!

广告
将在 10 秒后关闭
bannerAds