Nest+GrqphQL+Prisma+React全栈开发(一)-Graphql篇

1,520 阅读4分钟

在Nest+GrqphQL+Prisma+React全栈开发这个系列中,我将会带领着大家从零到一,一步一步简单学会使用这些技术栈,并实际开发一个实战应用。

这篇是第一篇文章,GraphQL篇。

一、初识GraphQL

(一)、简介

官方定义:GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。

说简单点,就是另一种接口查询数据的技术,和我们熟知的REST是同类的技术。

(二)、与REST对比

既然已经有了REST,为什么还需要GraphQL?

REST,即Representational State Transfer,翻译过来就是表现层状态转化。

RESTful架构定义:

  1. 每一个URI代表一种资源
  2. 客户端和服务器之间,传递这种资源的某种表现层,也就是conent-type的不同类型
  3. 客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化",也就是GET,POST这些

REST 的 API 配合JSON格式的数据交换,使得前后端分离、数据交互变得非常容易,因而也大受欢迎,发展迅猛。但是随之缺点也暴露了出来:

  1. 接口定义依赖文档,文档定义编写耗时,缺失则导致前端人员不知道该如何使用
  2. 设计不合理导致大量冗余重复相似度较高的接口出现
  3. 升级迭代困难,从前端到后台都需要全部修改一遍
  4. 一次性难以获得全部需要的数据,需要发起多个请求

基于以上这些问题,GraphQL则让前端自己描述需要的数据结构,后端返回对应的数据即可,是不是真香?

(三)、GraphQL的优缺点

1.优点

  1. 数据整体性强,REST所获取的数据都是离散的,如果要获取相关联的数据,则需要再发送一个新的接口,Graph只要在获取的时候直接定义就可以了
  2. 缓存强大,官方已经帮你实现好了,graphql.org/learn/cachi…
  3. 升级简单,不用修改不同的版本号接口
  4. 前后端沟通成本低
  5. 没有过度获取或者不足情况
  6. subscription 接受服务端推送,不必依靠轮询或者websocket
  7. 自省,可以查询 GraphQL 服务器支持的类型

2.缺点

  1. 复杂度,在获取接口的时候,如果不合理地嵌套太多字段,则容易引发服务端效率极差的查询
  2. 无法做查询频率限制

二、入门

(一)、基本概念

1. Operation

GraphQL的操作包括querymutationsubscription。 一般的操作格式是这样的

operation name(args) { 
   //数据类型
}

例如:

query HeroNameAndFriends {
  hero(id: "1000"){
    name
    friends {
      name
    }
  }
}

2. Schema

GraphQL有自己的一套类型语言,定义了字段的类型、数据的结构,描述了接口数据请求的规则。

# 对象类型
type Character {
  name: String!
  appearsIn: [Episode!]!
}

# 参数
type Starship {
  length(unit: LengthUnit = METER): Float
}

# 查询类型
type Query {
  hero(episode: Episode): Character
  droid(id: ID!): Droid
}

# 变更类型
type Mutation { 
  hero(episode: Episode): Character
  droid(id: ID!): Droid
}

# 标量类型
IntFloatStringBooleanID

# 枚举类型
enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

# 接口类型
interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

# 联合类型
union SearchResult = Human | Droid | Starship

# 输入类型
input ReviewInput {
  stars: Int!
  commentary: String
}

3. Execution

当我们进行查询的时候,后端还需要定义解析函数Resolver来提供数据。

例如一个query的解析函数:

Query: {
  human(obj, args, context, info) {
    return context.db.loadHumanByID(args.id).then(
      userData => new Human(userData)
    )
  }
}

解析器函数接收 4 个参数:

  • obj 上一级对象,如果字段属于根节点查询类型通常不会被使用。
  • args 传入的参数。
  • context 会被提供给所有解析器,并且持有重要的上下文信息
  • info 一个保存与当前查询相关的字段特定信息以及 schema 详细信息的值

4. Introspection

内省就是通过GraphQL可以查出支持哪些查询:

# 查询可用的类型
{
  __schema {
    types {
      name
    }
  }
}

# 检验特定类型
{
  __type(name: "...") {
    name
  }
}

# 查询有哪些对象
{
  __type(name: "...") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}

5. HTTP请求

例如查询GET请求:

{
  me {
    name
  }
}
# http请求为
http://myapi/graphql?query={me{name}}

POST请求为:

{
  "query": "...",
  "operationName": "...",
  "variables": { "myVariable": "someValue", ... }
}

返回的结果都是JSON的:

{
  "data": { ... },
  "errors": [ ... ]
}

6. 分页

在GraphQL中一般建议使用游标进行分页查询。

{
  hero {
    name
    friends(first:2, after:$friendId) {
      totalCount
      edges {
        node {
          name
        }
        cursor
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }
  }
}

三、在Nest中使用GraphQL

首先需要安装相应的包

$ npm i --save @nestjs/graphql graphql-tools graphql apollo-server-express@2.x.x

Nest 提供了两种构建 GraphQL 应用程序的方式,模式优先代码优先

代码优先,就是使用装饰器和 TypeScript 类来生成相应的 GraphQL schema。

模式优先,就是自己手写 GraphQL SDL(模式定义语言)。

安装完成之后配置app.module.ts

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';

@Module({
  imports: [
    GraphQLModule.forRoot({}),
  ],
})
export class ApplicationModule {}

四、在React中使用GraphQL

造react中使用GraphQL一般使用applo/client

npm install @apollo/client graphql

初始化:

const client = new ApolloClient({
  uri: 'https://48p1r2roz4.sse.codesandbox.io',
  cache: new InMemoryCache()
});

连接Client到React

render(
  <ApolloProvider client={client}>
    <App />  
  </ApolloProvider>,  
  document.getElementById('root'),

获取数据:

const EXCHANGE_RATES = gql`
  query GetExchangeRates {
    ...
  }
`;

function ExchangeRates() {
  const { loading, error, data } = useQuery(EXCHANGE_RATES);
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;

  return ...
  ));
}