GraphQL 既是一种用于 API 的查询言语也是一个满意你数据查询的运行时。 GraphQL 对你的 API 中的数据供给了一套易于理解的完好描绘,使得客户端能够精确地获得它需求的数据,而且没有任何冗余,也让 API 更容易地跟着时刻推移而演进,还能用于构建强大的开发者工具。

大概率你听说过 GraphQL,知道它是一种与 Rest API 架构归于 API 接口的查询言语。但大概率你也与我一样没有测验过 GraphQL。

事实上从 2012 年 Facebook 初次将 GraphQL 应用于移动应用,到 GraphQL 标准于 2015 年实现开源。可如今现状是 GraphQL 不温不火,时不时又有新的文章介绍,不知道的还以为是什么新技术。

:::tip 方针 本文将上手运用 GraphQL,并用 Nestjs 与 Strapi 这两个 Node 框架建立 GraphQL 服务。 :::

关于 GraphQL 介绍,详见官网 GraphQL | A query language for your API 或相关介绍视频 GraphQL 速览:React/Vue 的最佳搭档

GraphQL 与 Restful API 相比

GraphQL 实践与服务搭建

Restful API

Restful 架构的规划范式侧重于分配 HTTP 恳求办法(GET、POST、PUT、PA TCH、DELETE)和 URL 端点之间的关系。如下图

GraphQL 实践与服务搭建

可是实践杂乱的事务中,单靠 Restful 接口,需求发送多条恳求,例如获取博客中某篇博文数据与作者数据

GET /blog/1
GET /blog/1/author

要么单独另写一个接口,如getBlogAndAuthor,这样直接为调用方“定制”一个接口,恳求一条就得到就调用方想要的数据。可是另写一个getBlogAndAuthor 就破坏了 Restful API 接口风格,而且在杂乱的事务中,比如说还要获取博文的谈论等等,后端就要额定供给一个接口,能够说非常繁琐了。

有没有这样一个功能,将这些接口做一下聚合,然后将成果的调集回来给前端呢?在现在比较流行微服务架构体系下,有一个专门的中间层专门来处理这个作业,这个中间层叫 BFF(Backend For Frontend)。能够参看 BFF——服务于前端的后端

GraphQL 实践与服务搭建

但这些接口一般来说都比较重,里面有很多当时页面并不需求的字段,那还有没有一种恳求:客户端只需求发送一次恳求就能获取所需求的字段

有,也便是接下来要说的 GraphQL

GraphQL

GraphQL 实践与服务搭建

REST API 构建在恳求办法(method)和端点(endpoint)之间的连接上,而 GraphQL API 被规划为只经过一个端点,即 /graphql,始终运用 POST 恳求进行查询,其会集的 API 如 http://localhost:3000/graphql,一切的操作都经过这个接口来履行,这会在后面的操作中在展示到。

:::info 可是想要一条恳求就能得到客户端想要的数据字段,那么服务端必然要做比较多的使命(想想也是,后端啥都不干,前端就啥都能获取,怎么或许嘛)。

而服务端要做的便是建立一个 GraphQL 服务,这在后面也会操作到,也算是本文的要点。 :::

接下来便会在客户端中体验下 GraphQL,看看 GraphQL 究竟有多好用。

在线体验 GraphQL

能够到 官网 中简单测验入门一下,在 Studio 可在线体验 GraphQL,也能够到 [SWAPI GraphQL API](<swapi-graphql.netlify.app/?query={ person(personID: 1) { name } }> ‘SWAPI GraphQL API (swapi-graphql.netlify.app)’) 中体验。

下面以 apollographql 为例,并查询 People 目标。

query

查询一切 People 而且只获取 namegenderheight 字段

GraphQL 实践与服务搭建

查询 personID 为 1 的 Person 而且只获取 namegenderheight 字段

GraphQL 实践与服务搭建

查询 personID 为 2 的 Person 而且只获取 nameeyeColorskinColorhairColor 字段

GraphQL 实践与服务搭建

从上面查询事例中其实就能够发现,我只需求在 person 中写上想要获取的字段,GraphQL 便会回来带有该字段的数据。避免了回来成果中不必要的数据字段。

{
	person{ 
		# 写上想获取的字段 
	}
}

假如你不想要 person 数据或者想要其他其他的数据,不用像 Restful API 那样恳求多条接口,仍旧恳求/graphql,如

GraphQL 实践与服务搭建

:::info

无论你想要什么数据,一次恳求便可满意。

:::

mutation

GraphQL 的大部分评论会集在数据获取(也是它的强项),可是任何完好的数据平台也都需求一个改动服务端数据的办法。即 CRUD。

GraphQL 供给了 变更(Mutations) 用于改动服务端数据,不过 apollographql 在线示例中并没有如 createPeople 字段支撑 。这个片段在线体验中就无法体验到,后在后文中展示到。这里你只需求知道 GraphQL 能够履行根本的 CRUD 即可。

fragmen 和 subscribtion

此外还有 fragment subscription 就不做介绍。

小结

测验完上面这些操作后,能够非常明显的感受到 GraphQL 的优势与便利,本来是需求恳求不同的 url,现在只需求恳求 /graphql,对调用方(前端)来说非常友好,香是真的香。

可现在只是运用了他人装备好的 GraphQL 服务,让前端开发用了特别友好的 API。可是,关于后端开发而言,想要供给 GraphQL 服务可就不那么友善了。由于它不像传统的 restful 恳求,需求专门装备 GraphQL 服务,而整个进程是需求花费一定的作业量(界说 Schema,Mutations 等等),前面也提到想要一条恳求就能得到客户端想要的数据字段,那服务端必然需求额定的作业量。

不只需求在后端中装备 GraphQL 服务,用于接收 GraphQL 查询并验证和履行,此外前端一般需求 GraphQL 客户端,来方便运用 GraphQL 获取数据,现在有用比较多的是Apollo Graph,不过本文侧重建立GraphQL 服务,因此前端暂不演示怎么运用 GraphQL。

你或许听过一句话是,graphq​l 大部分时刻在折磨后端,而且要求比较严格的数据字段,可是好处都是前端。把作业量根本都丢给了后端,所以在遇到运用这门技术的公司,尤其是后端岗位就需求考虑有没有加班的或许了。

以下便会开始实践建立 GraphQL 服务,这里会用 Nest.js 与 Strapi 别离实践演示。

Nest.js

官方文档:GraphQL + TypeScript | NestJS

模块:nestjs/graphql

库房本文实例代码库房: kuizuo/nest-graphql-demo

创立项目

nest new nest-graphql-demo

装置依赖

npm i @nestjs/graphql @nestjs/apollo graphql apollo-server-express

修正 app.module.ts

import {Module} from '@nestjs/common';
import {GraphQLModule} from '@nestjs/graphql';
import {ApolloDriver, ApolloDriverConfig} from '@nestjs/apollo';
@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true,
    }),
  ],
})
export class AppModule {}

resolver

设置了autoSchemaFile: true ,nest.js 将会主动查找整个项目一切以 .resolver.ts 为后缀的文件,将其解析为 schema.gql 比如说创立app.resolver.ts

import {Resolver, Query} from '@nestjs/graphql';
@Resolver()
export class AppResolver {
  @Query(() => String) // 界说一个查询,而且回来字符类型
  hello() {
    return 'hello world';
  }
}

graphqlresolver 叫解析器,与 service 类似(也需求在 @Module 中经过 providers 导入)。resolver主要包含query(查询数据)、mutation(增、删、改数据)、subscription(订阅,有点类型 socket),在 graphql 项目中我们用 resolver 替换了之前的控制器。

这时候翻开http://127.0.0.1:3000/graphql,能够在右侧中看到主动生成的 Schema,这个 Schema 非常要害,决定了你客户端能够恳求到什么数据。

测验输入 GraphQL 的 query 查询(能够按 Ctrl + i 触发代码建议(Trigger Suggest),与 vscode 同理)

GraphQL 实践与服务搭建

此刻点击履行,能够得到右侧成果,即app.resolver.tshello 函数所界说的回来体。

GraphQL 实践与服务搭建

Code first 与 Schema first

在 nestjs 中有 Code first 与 Schema first 两种方法来生成上面的 Schema,从名字上来看,前者是优先界说代码会主动生成 Schema,而后者是传统方法先界说Schema。

在上面一开始的例子中是 Code First 方法,一般运用该方法即可,无需关心 Schema 是怎么生成的。下文也会以 Code First 方法来编写 GraphQL 服务。

也可到官方示例库房中 nest/sample/31-graphql-federation-code-first 和 nest/sample/32-graphql-federation-schema-first 检查两者代码上的区别。

快速生成 GraphQL 模块

nest 供给 cli 的方法来快速生成 GraphQL 模块

nest g resource <name>

GraphQL 实践与服务搭建

比如创立一个 blog 模块

nest g resource blog --no-spec
? What transport layer do you use? GraphQL (code first)
? Would you like to generate CRUD entry points? Yes
CREATE src/blog/blog.module.ts (217 bytes)
CREATE src/blog/blog.resolver.ts (1098 bytes)
CREATE src/blog/blog.resolver.spec.ts (515 bytes)
CREATE src/blog/blog.service.ts (623 bytes)
CREATE src/blog/blog.service.spec.ts (446 bytes)
CREATE src/blog/dto/create-blog.input.ts (196 bytes)
CREATE src/blog/dto/update-blog.input.ts (243 bytes)
CREATE src/blog/entities/blog.entity.ts (187 bytes)
UPDATE src/app.module.ts (643 bytes)

便会生成如下文件

GraphQL 实践与服务搭建

import {Resolver, Query, Mutation, Args, Int} from '@nestjs/graphql';
import {BlogService} from './blog.service';
import {Blog} from './entities/blog.entity';
import {CreateBlogInput} from './dto/create-blog.input';
import {UpdateBlogInput} from './dto/update-blog.input';
@Resolver(() => Blog)
export class BlogResolver {
  constructor(private readonly blogService: BlogService) {}
  @Mutation(() => Blog)
  createBlog(@Args('createBlogInput') createBlogInput: CreateBlogInput) {
    return this.blogService.create(createBlogInput);
  }
  @Query(() => [Blog], {name: 'blogs'})
  findAll() {
    return this.blogService.findAll();
  }
  @Query(() => Blog, {name: 'blog'})
  findOne(@Args('id', {type: () => Int}) id: number) {
    return this.blogService.findOne(id);
  }
  @Mutation(() => Blog)
  updateBlog(@Args('updateBlogInput') updateBlogInput: UpdateBlogInput) {
    return this.blogService.update(updateBlogInput.id, updateBlogInput);
  }
  @Mutation(() => Blog)
  removeBlog(@Args('id', {type: () => Int}) id: number) {
    return this.blogService.remove(id);
  }
}

此刻 Schema 如下

GraphQL 实践与服务搭建

不过nest cli创立的blog.service.ts 只是示例代码,并没有实践事务的代码。

此外blog.entity.ts也不为数据库实体类,因此这里引进typeorm,并运用sqlite3

集成 Typeorm

装置依赖

pnpm install @nestjs/typeorm typeorm sqlite3
import {Module} from '@nestjs/common';
import {AppController} from './app.controller';
import {AppService} from './app.service';
import {GraphQLModule} from '@nestjs/graphql';
import {ApolloDriver, ApolloDriverConfig} from '@nestjs/apollo';
import {AppResolver} from './app.resolver';
import {BlogModule} from './blog/blog.module';
import {TypeOrmModule} from '@nestjs/typeorm';
@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'db.sqlite3',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true,
      playground: true,
    }),
    AppModule,
    BlogModule,
  ],
  controllers: [AppController],
  providers: [AppService, AppResolver],
})
export class AppModule {}

blog.entity.ts 改成实体类,代码为

import {ObjectType, Field} from '@nestjs/graphql';
import {
  Column,
  Entity,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
} from 'typeorm';
@ObjectType()
@Entity()
export class Blog {
  @Field(() => Int)
  @PrimaryGeneratedColumn()
  id: number;
  @Field()
  @Column()
  title: string;
  @Field()
  @Column({type: 'text'})
  content: string;
  @Field()
  @CreateDateColumn({name: 'created_at', comment: '创立时刻'})
  createdAt: Date;
  @Field()
  @UpdateDateColumn({name: 'updated_at', comment: '更新时刻'})
  updatedAt: Date;
}

其间 @ObjectType() 装修器让 @nestjs/graphql 主动让其视为一个 type Blog

@Field() 则是作为可展示的字段,比如 password 字段无需回来,就不必要加该装修器。

:::caution

@nestjs/graphql 会将 typescript 的 number 类型视为 Float,所以需求转成 Int 类型,即 @Field(() => Int)

:::

为 BlogService 编写 CRUD 数据库事务代码,并在 dto 编写参数效验代码,这里简单暂时部分代码。

import {Injectable} from '@nestjs/common';
import {InjectRepository} from '@nestjs/typeorm';
import {Repository} from 'typeorm';
import {CreateBlogInput} from './dto/create-blog.input';
import {UpdateBlogInput} from './dto/update-blog.input';
import {Blog} from './entities/blog.entity';
@Injectable()
export class BlogService {
  constructor(
    @InjectRepository(Blog)
    private blogRepository: Repository<Blog>,
  ) {}
  create(createBlogInput: CreateBlogInput) {
    return this.blogRepository.save(createBlogInput);
  }
  findAll() {
    return this.blogRepository.find();
  }
  findOne(id: number) {
    return this.blogRepository.findOneBy({id});
  }
  async update(id: number, updateBlogInput: UpdateBlogInput) {
    const blog = await this.blogRepository.findOneBy({id});
    const item = {...blog, ...updateBlogInput};
    return this.blogRepository.save(item);
  }
  remove(id: number) {
    return this.blogRepository.delete(id);
  }
}
import {InputType, Field} from '@nestjs/graphql';
@InputType()
export class CreateBlogInput {
  @Field()
  title: string;
  @Field()
  content: string;
}

此刻

GraphQL 实践与服务搭建

CRUD

下面将演示 graphql 的 Mutation。

新增

GraphQL 实践与服务搭建

修正

GraphQL 实践与服务搭建

删除

GraphQL 实践与服务搭建

Query 就不在演示。

小结

至此,在 Nest.js 中装备 GraphQL 服务的就演示到此,从这里来看,Nest.js 装备 GraphQL 服务还算比较轻松,可是做了比较多的作业量,创立 resolver,创立 modal(或在已有实体添加装修器),不过本文事例中只演示了根本的 CRUD 操作,实践事务中还需求触及鉴权,限流等等。

Strapi

Strapi 官方供给 GraphQL 插件 免去了装备的繁琐。更详细的装备拜见 GraphQL – Strapi Developer Documentation

这里我就选用 kuizuo/vitesse-nuxt-strapi 作为演示,并为其供给 graphQL 支撑。

strapi 装置

npm install @strapi/plugin-graphql

接着启动 strapi 项目,并在浏览器翻开 graphql 控制台 http://localhost:1337/graphql,以下将演示几个应用场景。

例子

查询一切 todo

GraphQL 实践与服务搭建

查询 id 为 2 的 todo

GraphQL 实践与服务搭建

查询 id 为 2 的 todo 并只回来 value 属性

GraphQL 实践与服务搭建

新增 todo

GraphQL 实践与服务搭建

更新 todo

GraphQL 实践与服务搭建

删除 todo

GraphQL 实践与服务搭建

由于 Nuxt Strapi 供给 useStrapiGraphQL 能够非常方便是在客户端调用 GraphQL 服务。

<script setup lang="ts">
  const route = useRoute();
  const graphql = useStrapiGraphQL();
  // Option 1: use inline query
  const restaurant = await graphql(`
  query {
    restaurant(id: ${route.params.id}) {
      data {
        id
        attributes {
          name
        }
      }
    }
  }
`);
  // Option 2: use imported query
  const restaurant = await graphql(query, {id: route.params.id});
</script>

小结

关于 Strapi 来说,建立 GraphQL 服务根本没有装备的负担,装置一个插件,即可合作 Strapi 的 content-type 来供给 GraphQL 服务。

总结

GraphQL 翻译过来为 图表 Query Language,我所理解的理念是经过 json 数据格式的方法去写 SQL,而且有种前端人员在写 sql 句子。在我看来 GraphQL 更多是事务数据特别仿制的情况下运用,往往能够事半功倍。但关于本文中示例的代码而言,GraphQL 反倒有点过于先进了。

如今看来,GraphQL 还处于不温不火的状态,现在更多的站点主流仍是运用 Restful API 架构。我不过我猜测,主要仍是大多数事务没有 API 架构的升级的需求,原有的 Restful API 虽然不够优雅,可是也能够满意事务的需求,反而 GraphQL 是一个新项目 API 架构的挑选,但不是一个必须的挑选。

至于怎么挑选,能够参看官方 GraphQL 最佳实践,至于说有没有必要学 GraphQL,这篇文章 都快 2022 年了 GraphQL 还值得学吗 能给你答案。我的建议是了解即可,新项目能够考虑运用,就别想着用 GraphQL 来重构原有的 API 接口,作业量将会非常巨大,而且还或许是费力不讨好的事。反正我以为这门技术不像 Git 这种归于必学的技术,我的五星评分是⭐⭐

但多了解一门技术,便是作业面试的资本。回想我为何测验 GraphQL,便是由于我无意间看到了一份 ts 全栈的长途面试招聘,在这份招聘单中写到 【会 graphql 编写是加分项】。所以抱着这样的态度去测验了一番,说不准未来便是由于 graphql 让我拿到该 offer。当然也是由于很早之前就听闻 GraphQL,想亲手目睹下是否有所谓的那么奇特。