GraphQL 实战篇之前端Vue 后端

(给前端大学加星标,提升前端技能.)

作者:PatWu16

https://segmentfault.com/a/1190000039087433

这篇文章记录使用Vue+GraphQL搭建客户端。

客户端项目目录结构如下:

安装

首先我们先使用vue-cli新建项目,接着安装依赖:

npm install apollo-cache-inmemory apollo-client apollo-link apollo-link-http apollo-link-ws apollo-utilities vue-apollo -S

引入依赖

// main.jsimport Vue from 'vue'import App from './App.vue'import { apolloProvider } from './vue-apollo';

Vue.config.productionTip = false

new Vue({    render: h => h(App),    // 像 vue-router 或 vuex 一样注入apolloProvider     apolloProvider,}).$mount('#app')// vue-apollo.js// 相关文档请查阅 https://apollo.vuejs.org/zh-cn/import { ApolloClient } from 'apollo-client'import { createHttpLink } from 'apollo-link-http'import { InMemoryCache } from 'apollo-cache-inmemory'import Vue from 'vue'import VueApollo from 'vue-apollo'// 新的引入文件import { split } from 'apollo-link'import { WebSocketLink } from 'apollo-link-ws'import { getMainDefinition } from 'apollo-utilities'

Vue.use(VueApollo)

// 与 API 的 HTTP 连接const httpLink = createHttpLink({ // 你需要在这里使用绝对路径     uri: 'http://localhost:3001/graphql',})// 创建订阅的 websocket 连接const wsLink = new WebSocketLink({    uri: 'ws://localhost:3001/graphql',    options: {        reconnect: true,    }})// 使用分割连接的功能// 你可以根据发送的操作类型将数据发送到不同的连接const link = split(({ query }) => {    const definition = getMainDefinition(query)    return definition.kind === 'OperationDefinition' && definition.operation === 'subscription'    },    wsLink,    httpLink)// 创建 apollo 客户端const apolloClient = new ApolloClient({    link,    cache: new InMemoryCache(),    connectToDevTools: true,})

export const apolloProvider = new VueApollo({           defaultClient: apolloClient,})

编写业务代码

// App.vue
<template>
 <div id='app'> <img alt='Vue logo' src='./assets/logo.png'> <HelloGraphQL /> </div></template>
<script>
import HelloGraphQL from './components/HelloGraphQL.vue'

export default {
    name: 'app',
    components: {
        HelloGraphQL
    }
}
</script>
<style>
#app {
    font-family: 'Avenir',Helvetica, Arial, sans-serif;     -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale; 
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
}
</style>
// HelloGraphQL.vue
<template>
    <div class='hello'>
        作者姓氏:{{author.firstName}}
        <button @click='changeAuthor'>修改作者姓氏</button>
        <br> <br> 
        新增标题:{{post.title}}
        <button @click='addPost'>添加文章</button>
        <br> 
        订阅成功次数:{{receivedSubscriptionTimes}}
    </div>
</template>
<script>
import gql from 'graphql-tag'
export default {
    name: 'HelloGraphQL',
    data: function (){
        return {
            author: {},
            post: {},
            receivedSubscriptionTimes: 0
        } 
    },
    apollo: {
        // Apollo 的具体选项
        author: gql`query author {
            author(id: 2) {
                id,
                firstName,
                posts {
                    id,
                    title
                }
            }
        }`,
        $subscribe: {
            postAdded: {
                query: gql`subscription postAdded{
                    postAdded {
                        id, 
                        title
                    }
                }`, 
                // 变更之前的结果
                result ({ data }) {
                    // 在这里用之前的结果和新数据组合成新的结果
                    // eslint-disable-next-line         
                    console.log(data)
                    this.receivedSubscriptionTimes += 1                 }
            }
        }
    },
    methods: {
        // 修改作者
        changeAuthor() {
            // 调用 graphql 变更
            this.$apollo.mutate({
                // 查询语句
                mutation: gql`mutation changeAuthor {
                    changeAuthor(id: 3, firstName: 'firstName' lastName: 'lastName') {
                        id,
                        firstName,
                        lastName
                    }
                }`,
                // 参数
                variables: {
                    firstName: '',
                },
            }).then(res => {
                this.author.firstName = res.data.changeAuthor.firstName;
            }) 
        },
        // 添加文章
        addPost() {
            // 调用 graphql 变更
            this.$apollo.mutate({
                // 查询语句
                mutation: gql`mutation addPost {
                    post: addPost {
                        id,
                        title
                    }
                }`
            }).then(res => {
                this.post = res.data.post;
            })
        }
    }
}
</script>

至此,我们便可以请求server端服务了!🎉🎉🎉

前面我们介绍了GraphQL的概念和基础知识,这篇文章记录下使用Nestjs+GraphQL搭建Node服务。

安装

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

注册

// app.module.ts
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { ConfigModule, ConfigService } from 'nestjs-config';
@Module({
 imports: [
    ConfigModule.load(path.resolve(__dirname, 'config', '**/!(*.d).{ts,js}')),
    GraphQLModule.forRootAsync({
        imports: [ConfigModule],
        useFactory: (config: ConfigService) => config.get('graphql'),
        inject: [ConfigService],
    }) 
 ],})
export class ApplicationModule {}

// src/config/graphql.ts
import * as path from 'path';
export default {
  autoSchemaFile: path.join(process.cwd(), 'src/schema.gql'), // 最后生成的`Schema 文件,不可修改`
  installSubscriptionHandlers: true, // 启用订阅
};

启动项目,并访问 http://localhost:3000/graphql,我们便可以看到graphql页面。

编写服务端逻辑

接下来我们注册一个author模块,目录结构如下:

// author.module.tsimport { Module } from '@nestjs/common';import { AuthorService } from './author.service';import { AuthorResolver } from './author.resolver';@Module({  providers: [    AuthorService,     AuthorResolver  ]})export class AuthorModule {}// author.service.ts// 此文件用于写数据库查询等逻辑,我们着重学习GraphQL的使用,故此处不做相关Demoimport { Injectable } from '@nestjs/common';@Injectable()export class AuthorService {  async findOneById() {}}// author.resolver.tsimport { Args, Mutation, Query, Resolver, Subscription, ResolveField, Parent, Int } from '@nestjs/graphql';import { PubSub } from 'graphql-subscriptions';import { Author } from './models/author.model';import { Post } from './models/post.model';import { AuthorService } from './author.service';// import { GetAuthorArgs } from './dto/get-author.args';

const pubSub = new PubSub();

@Resolver(() => Author)export class AuthorResolver {  constructor(     private authorsService: AuthorService  ) {} // 根据id查询作者信息 @Query(returns => Author, {    name: 'author',    description: 'get author info by id',    nullable: false }) async getAuthor(@Args('id', {     type: () => Int,     description: 'author id',     nullable: false }) id: number): Promise<any> { // return this.authorsService.findOneById(id);    return {        id,        firstName: 'wu',        lastName: 'pat',    };}// 使用DTO接受参数// @Query(returns => Author)// async getAuthor(@Args() args: GetAuthorArgs) {//   return this.authorsService.findOneById(args);// } // 修改作者信息 @Mutation(returns => Author, {    name: 'changeAuthor',    description: 'change author info by id',    nullable: false }) async changeAuthor(    @Args('id') id: number,    @Args('firstName') firstName: string,    @Args('lastName') lastName: string, ): Promise<any> { // return this.authorsService.findOneById(id); return {    id,    firstName,    lastName,  };}

 // 解析posts字段 @ResolveField() async posts(@Parent() author: Author): Promise<any> {     const { id } = author;     // return this.postsService.findAll({ authorId: id });     return [{        id: 4,        title: 'hello',        votes: 2412,     }]; }

 // 新增文章 @Mutation(returns => Post) async addPost() {     const newPost = {        id: 1,        title: '新增文章'     };     // 新增成功后,通知更新     await pubSub.publish('postAdded', { postAdded: newPost });     return newPost; }

 // 监听变更 @Subscription(returns => Post, {     name: 'postAdded',     // filter: (payload, variables) => payload.postAdded.title === variables.title,     // 过滤订阅     // resolve(this: AuthorResolver, value) { // 修改payload参数     //   return value;     // } }) async postAdded(/*@Args('title') title: string*/) {    return (await pubSub.asyncIterator('postAdded')); }}// author.model.tsimport { Field, Int, ObjectType } from '@nestjs/graphql';import { Post } from './post.model';

@ObjectType({ description: 'Author model' })export class Author { @Field(type => Int, {    description: '作者id' }) id: number;

 @Field({    nullable: true,    description: '作者姓姓氏' }) firstName?: string;

 @Field({     nullable: true,    description: '作者姓名字' }) lastName?: string;

 // 要声明数组的项(而不是数组本身)是可为空的,请将nullable属性设置'items' // 如果数组及其项都是可空的,则设置nullable为'itemsAndList'  @Field(type => [Post], {    nullable: 'items',    description: '作者发表的文章' }) posts: Post[];}// posts.model.tsimport { Field, Int, ObjectType } from '@nestjs/graphql';

@ObjectType()export class Post {

 @Field(type => Int) id: number;

 @Field() title: string;

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

上面的代码包含了查询、变更、订阅类型,此时我们会发现src下面新增了一个文件schema.gql,这个文件就是自动生成的类型文件:

# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------
type Post {
    id: Int!
    title: String!
    votes: Int
}

'''Author model'''
type Author {
    '''作者id'''
    id: Int!
    
    '''作者姓姓氏'''
    firstName: String
    
    '''作者姓名字'''
    lastName: String
    
    '''作者发表的文章'''
    posts: [Post]!
}

type Query {
    '''get author info by id'''
    author(
        '''author id'''
        id: Int!
    ): Author!
}

type Mutation {
    '''change author info by id''' 
    changeAuthor(lastName: String!, firstName: String!, id: Float!): Author!
    addPost: Post!
}

type Subscription {
    postAdded: Post!
}

执行查询

这时我们的服务已经运行起来,可以执行查询了。

# 左下角编写QUERY VARIABLES{    'id': 1}# Write your query or mutation here# 查询作者信息query author($id: Int!) {    alias: author(id: $id) {        id,        firstName,        posts {            id,            title        }    }}

# 修改作者信息mutation changeAuthor {    changeAuthor(id: 3, firstName: 'firstName' lastName: 'lastName') {        id,        firstName,        lastName    }}

# 发布文章mutation addPost {    post: addPost {        id,        title    }}

# 订阅文章新增subscription postAdded{    postAdded {        id,        title    }}

// 自省查询query schema{    __schema {        types {             name        }    }}

至此,我们的Nestjs+GraphQL服务便搭建完成,给自己一个👍!

前端大学

分享前端IT干货,最新前端技术!JS,HTML5,CSS3,Vue.js,React,Angular前端框架,前端项目实战,视频教程,学习经验。 远不止web前端开发,更有各种互联网干货,和20万人一起学习IT技术,了解最新IT前沿知识!
23篇原创内容
公众号
(0)

相关推荐

  • 后端Django + 前端Vue.js快速搭建web项目

    参考网上一篇文章做了点细节补充. 本篇使用Vue.js作为前端框架,代替Django本身较为孱弱的模板引擎,Django则作为服务端提供api接口,使得前后端实现完全分离,更适合单页应用的开发构建. ...

  • 四物汤实战篇!(加减脏腑病妇科病)

    一.四物汤加减脏腑病 1 血虚腹痛.微汗恶风.加官桂(七分).倍芍药. 2 嗽痰.加桑白皮.杏仁.麻黄.贝母( 各等分). 3 大便秘.加桃仁.大黄.麻仁.枳壳(减半余各等分). 4 血虚头眩.加天麻 ...

  • 分时图看盘技巧——实战篇1

    分时图看盘技巧——实战篇1

  • 秘传八字推命的十大法则(高级实战篇)

    八字推命的十大法则 八字命理的起源,和八字的起源不同.甲子历法中对年月日时计时,年.月.日.时均有干支两个字组成,简称八字.这种甲子历法在中国据考从伏羲氏就开始有创建,从中国古代的夏朝就已经开始使用. ...

  • 成立业主委员会攻略——实战篇

    很多人都纳闷,一个美食公众号,为啥会去做业主委员会这个版块?其实也很简单,宛南美食密探这个公众号注册认证的就是三个版块儿,美食.业委会.汉服. 起初,主编杨表哥也是南阳市一个大型小区的业委会主任,三年 ...

  • 实战录播课 | 数据云和后端交互本地存储(四)

    .点击视频连接:APICloud生态 今天播放00:01/01:20:10APICloud的<APP和小程序实战开发系列课>共五节,本节内容如下:四.数据云和后端交互本地存储1.数据云3. ...

  • 实战篇:活血化瘀18法!

    姜春华先生从丰富的临床实践中,拟定了活血化瘀十八法,即活血清热法.活血解毒法.活血益气法.活血补血法.活血养阴法.活血助阳法.活血理气法.活血攻下法.活血凉血法.活血止血法.活血开窍法.活血利水法.活 ...

  • 杨清娟盲派八字命理实战篇 盲派说法

    八字五行在中华民族传统文化之中其实一直有着重要的席位,因为每一个人都有自己的生辰八字和五行命理,并且在人们的生活之中.通过对八字和五行的分析,人们可以进一步了解自己在未来的发展过程之中,究竟会有什么样 ...

  • 出现这四种k线形态 就是买入良机(实战篇)

    █阶梯式买入法 这种K线组合是指在股价的上行趋势中出现中继缩量调整区,在这段时间内(一般为5天左右),K线没有大的变化,以小K线或十字线阴阳相伴运行,我们可以选择在整理区间内买入待涨. 图(1) 由图 ...