Nest - Graphql的使用

市面上介绍Graphql的文章不知凡几,好坏都有。那为什么今天还要介绍Nest当中的Grahpql的使用方法呢?

  • 无需学习成本
    Graphql实际上有自己的一套语言规范,例如schema规范等等,所以需要一定量的额外学习成本,这也是国内Graphql落地较少的原因之一。 而Nest解决了这个问题,其schema定义包括查询语法等基本和其自身的接口查询完美融合,只要会开发Nest接口,自然而然的也就会开发Graphql接口。
  • 天然适配BFF层
    现在很多公司开始使用微服务架构,数据也不再是简单的left join。会根据业务拆分服务,数据存在不同的数据库中。 而前端页面展示的数据也可能是从不同的服务(接口或者各类DB)中组装而成。而Graphql只需定义完Schema后,按需每个field自己去取就可以了。(但是这里的数据拆分包括DB拆分其实需要高端解耦人才,性能也要考虑,不过这个交给后端去考虑吧)

我们先来看下graphql的界面接口调试图:


image.png

界面的左半部分就是查询语法,而右面就是我们定义的Schema,包括了查询格式,返回值格式,字段类型等等。

现在我们开始,首先明确需求,我们有两个数据表,一张是作者表,一张是文章表。我们要实现几个接口:

  • 作者列表
  • 根据作者id获取作者
  • 修改文章数据
  • 订阅修改文章数据的操作

前期准备:

  • 安装包
npm i --save @nestjs/graphql apollo-server-express graphql
  • 准备数据
// author.ts 
import { Author } from '../graphqlSchema/author';
const aurthors: Author[] = [
    {
        id: 1,
        firstName: '孙',
        lastName: '悟空',
        posts: [],
    },
    {
        id: 2,
        firstName: '猪',
        lastName: '八戒',
        posts: [],
    },
    {
        id: 3,
        firstName: '沙',
        lastName: '悟净',
        posts: [],
    },
    {
        id: 4,
        firstName: '唐',
        lastName: '僧',
        posts: [],
    },
    {
        id: 5,
        firstName: '白',
        lastName: '龙马',
        posts: [],
    },
    {
        id: 6,
        firstName: '哪吒',
        lastName: '三台子',
        posts: [],
    },
    {
        id: 7,
        firstName: '李',
        lastName: '靖',
        posts: [],
    },
    {
        id: 8,
        firstName: '太乙',
        lastName: '真人',
        posts: [],
    },
    {
        id: 9,
        firstName: '元始',
        lastName: '天尊',
        posts: [],
    },
    {
        id: 10,
        firstName: '申',
        lastName: '公豹',
        posts: [],
    },
    {
        id: 11,
        firstName: '龙',
        lastName: '王',
        posts: [],
    },
];

export default aurthors;

// posts.ts
import { Post } from '../graphqlSchema/post';

const posts: Post[] = [
    {
        id: 1,
        title: '三打白骨精',
        votes: 4,
    },
    {
        id: 1,
        title: '真假大圣',
        votes: 4,
    },
    {
        id: 6,
        title: '大闹龙宫',
        votes: 5,
    },
];

export default posts;

那第一步,我们先从定义Schema开始。

// author.ts
import { Field, Int, ObjectType } from 'type-graphql';
import { Post } from './post';

@ObjectType()
export class Author {
    @Field(type => Int)
    id: number;  // 作者id

    @Field({ nullable: true })
    firstName?: string; // 姓

    @Field({ nullable: true })
    lastName?: string; // 名

    @Field(type => [Post])
    posts: Post[];  // 文章列表
}

// post.ts
import { Field, Int, ObjectType } from 'type-graphql';

@ObjectType()
export class Post {
    @Field(type => Int)
    id: number;  // 作者id

    @Field()
    title: string;  // 文章标题

    @Field(type => Int, { nullable: true })
    votes?: number;  // 投票数
}

我们前面说过Nest中的Graqhql无需学习额外的语法。所以Schema也和Nest普通定义dto差不多。 只不过上面@ObjectType是用来标示这是Graphql的Schema,表示实体,返回的数据格式。 nullable代表是否可为空。

第二步我们来定义接口的入参,既然是入参,我们当然还要判断入参的类型等等是否符合规范,这里面统一用了class-validator来判断。 不会的请去github自行学习。

// author.args.ts
import { Min, IsNotEmpty } from 'class-validator';
import { ArgsType, Field, Int } from 'type-graphql';

@ArgsType()
export class AuthorArgs {
    @Field(type => Int)
    @IsNotEmpty()
    @Min(0)
    // pageIndex: number = 0;
    pageIndex;
}

// post.args.ts
import { InputType, Field } from 'type-graphql';
import { IsNotEmpty } from 'class-validator';

@InputType()
export class UpvotePostInput {
    @Field()
    @IsNotEmpty()
    id: number;

    @Field()
    @IsNotEmpty()
    votes: number;
}

我们可以看到入参的schema其实和Nest的dto也仍是一致。 @ArgsType这时就代表了该类实际是graphql的查询接口的参数格式。 @InputType则是修改或者插入接口的参数格式。

下来就是编写service,实际的业务操作

// author.service.ts
import { Injectable } from '@nestjs/common';
import { Author } from '../graphqlSchema/author';
import * as authors from '../models/author';
import * as _ from 'lodash';
import { AuthorArgs } from '../dto/author.args';

@Injectable()
export class AuthorsService {
    // 查询作者列表,简单的从已经预先创建好的数据进行分块,每块10条数据
    authorList(authorArgs: AuthorArgs): Author[] {
        const chunk = _.chunk(authors.default, 10);
        return chunk[authorArgs.pageIndex];
    }
 
    // 根据id查询作者
    findOne(id: number): Author {
        return _.find(authors.default, (item) => item.id === id);
    }
}

// posts.service.ts
import { Injectable } from '@nestjs/common';
import { Post } from '../graphqlSchema/post';
import * as posts from '../models/posts';
import * as _ from 'lodash';
import { UpvotePostInput } from '../dto/post.args';

@Injectable()
export class PostsService {
    // 根据作者id查询所有文章
    findAll(id): Post[] {
        return _.filter(posts.default, (item) => item.id === id);
    }

    // 根据id修改该文章的投票数
    upvoteById(upvotePostInput: UpvotePostInput): Post {
        const post = _.find(posts.default, (item) => item.id === upvotePostInput.id);
        post.votes = upvotePostInput.votes;
        return post;
    }
}

service部分大功告成,下来就是编写resolver层了,这里和Nest的Controller层大同小异,写法基本也相同

import { Args, Mutation, Query, Resolver, Subscription, ResolveProperty, Parent } from '@nestjs/graphql';
import { PubSub } from 'apollo-server-express';
import { Author } from '../graphqlSchema/author';
import { AuthorsService } from '../service/author.service';
import { PostsService } from '../service/posts.service';
import { AuthorArgs } from '../dto/author.args';
import { Post } from '../graphqlSchema/post';
import { UpvotePostInput } from '../dto/post.args';

const pubSub = new PubSub();

@Resolver(of => Author)
export class AuthorResolver {
    constructor(
        private readonly authorsService: AuthorsService,
        private readonly postsService: PostsService,
    ) { }

    // 第一个查询节点,名字叫做authorList,可以对比最上面的graphql界面图
    @Query(returns => [Author])
    // 作者列表需要分页,所以穿个pageIndex,我们已经在上面定义过了
    authorList(@Args() authorArgs: AuthorArgs): Author[] {
        return this.authorsService.authorList(authorArgs);
    }

    // 修改文章的投票数,graphql中修改操作是叫Mutation
    @Mutation(returns => Post)
    // 修改的节点叫做upvotePost
    upvotePost(@Args('upvotePostData') upvotePostInput: UpvotePostInput) {
        const post = this.postsService.upvoteById(upvotePostInput);
        // graphql中有个操作叫做订阅,即前端如果订阅了某个事件(这里是postupdated),则当有此事件发布时,
        // 前端会收到通知,实际上是通过websocket实现的。 这里即当投票数修改时,会发送此事件。
        pubSub.publish('postupdated', { postupdated: post });
        return post;
    }

    // 第二个查询节点。即根据id查询作者
    @Query(returns => Author)
    // 如果参数不多,不必定义类似dto的schema,直接写也可
    author(@Args('id') id: number): Author {
        return this.authorsService.findOne(id);
    }
    
    // 这里就是posts这个filed单独查询数据
    @ResolveProperty()
    // 根据父元素的id去查询
    posts(@Parent() author) {
        const { id } = author;
        return this.postsService.findAll(id);
    }
  
    // 前端的订阅接口
    @Subscription(returns => Post)
    postupdated() {
        return pubSub.asyncIterator('postupdated');
    }
}

Nest最大特色就是依赖注入,所以我们还要写module。

// author.graphql.module.ts
import { Module } from '@nestjs/common';
import { PostsModule } from './posts.graphql.module';
import { AuthorsService } from '../service/author.service';
import { AuthorResolver } from '../resolver/author.resolver';

@Module({
    imports: [PostsModule],  // 因为AuthorResolver中用到了postService,所以要导入该模块
    providers: [AuthorsService, AuthorResolver],
})
export class AuthorsModule { }

// posts.graphql.module.ts
import { Module } from '@nestjs/common';
import { PostsService } from '../service/posts.service';

@Module({
    providers: [PostsService],
    exports: [PostsService], // 因为有别的模块需要使用,所以要导出该模块
})
export class PostsModule { }

最后在app.module.ts这个根模块中,我们要开启graphql模块。

@Module({
  imports: [
     AuthorsModule,
     GraphQLModule.forRoot({
        debug: false,
        playground: true,  // 开启调试界面
        autoSchemaFile: './schema.gql', // 放个该名字的空文件,底层会读取Nest形式的schema然后生成graphql原始的sehema里面
        installSubscriptionHandlers: true, // 使用订阅就要开启这个参数
     })
 ],
  controllers: [AppController],
  providers: [AppService],
})

大功告成!

你可能感兴趣的:(Nest - Graphql的使用)