GraphQL 實戰篇之前端Vue+後端

2021-03-02 前端教程

這篇文章記錄使用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.js
import 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.ts
import { 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的使用,故此處不做相關Demo
import { Injectable } from '@nestjs/common';
@Injectable()
export class AuthorService {
  async findOneById() {}
}
// author.resolver.ts
import { 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.ts
import { 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.ts
import { 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服務便搭建完成,給自己一個👍!

相關焦點

  • GraphQL 入門看這篇就夠了
    graphql + graph-pack 的組合實戰應用 GraphQL,詳細闡述如何使用 GraphQL 來進行增刪改查和數據訂閱推送,並附有使用示例,邊用邊學印象深刻~如果希望將 GraphQL 應用到前後端分離的生產環境,請期待後續文章。
  • GraphQL 從入門到實踐
    正文從這開始~~本文首先介紹了 GraphQL,再通過 MongoDB + graphql + graph-pack 的組合實戰應用 GraphQL,詳細闡述如何使用 GraphQL 來進行增刪改查和數據訂閱推送,並附有使用示例,邊用邊學印象深刻~0. 什麼是 GraphQLGraphQL 是一種面向數據的 API 查詢風格。
  • GraphQL入門指南
    GraphQL也並不是一個具體的後端編程框架,如果將REST看做適合於簡單邏輯的查詢標準,那麼GraphQL可以做一個獨立的抽象層,通過對於多個REST風格的簡單的接口的排列組合提供更多複雜多變的查詢方式。與REST相比,GraphQL定義了更嚴格、可擴展、可維護的數據查詢方式。
  • GraphQL 簡介:原理及其使用
    新建一個名為 graphql-with-nodejs 的文件夾,進入項目文件夾並運行 npm init 來創建 NodeJS 項目,終端命令如下:cd graphql-with-nodejsnpm init安裝依賴項使用以下命令安裝 Express:npm install express
  • 基於 Spring Boot + Vue.js + MySQL 的 QQ 登陸實戰
    這篇文章就是解決這個事情。此文基於OAuth2 協議開發 QQ 聯合登錄實戰過程,在學習本篇內容前您需要提前了解:前後端分離開發模式vue.js 基礎語法 比如 axios、事件綁定等相關知識後端開發、資料庫等相關基礎知識如果您已經具備了以上所述那我們就開搞吧!
  • GraphQL|一種配得上凡爾賽的API框架
    在知道答案之前,我們先來了解以下graphql。以下是graphql的官方站:https://graphql.cn/通過官方的實例,我們可以知道,graphql的主要功能,是進行API測試,與其他的API測試工具相比,graphql有幾個優勢:1、graphql可以通過圖形化界面的方式,在同一個界面上交互式地進行API數據測試,
  • GraphQL 值得了解一下
    GraphQL 是後端數據查詢語言,可以簡單理解為 GraphQL 對標的是 REST 接口。GraphQL 由 Facebook 開源,目前已經在 Facebook 中支撐千億級的 API 接口調用,在 Facebook 之外正在被迅速應用。
  • GraphQL 基礎實踐
    將它安裝到我們的項目中:npm install apollo-server-core graphql --save編寫中間件runHttpQuery主要接受兩個參數,第一個是 GraphQLServerOptions,這個我們可以不需要配置,留空數組即可;第二個是HttpQueryRequest對象,我們至少需要包含 methods,options以及query,
  • GraphQL 概念與測試(下):graphy測試框架
    graphql測試思路於是我們可以歸納graphql接口的測試思路: sgqlc:官方的python客戶端sgqlc是graphql推薦的官方python客戶端,它的github地址是:https://github.com/profusion/sgqlc。
  • 【超詳細教程】如何使用TypeScript和GraphQL開發應用
    fantingshengdeMacBook-Pro:graphql-typescript fantingsheng$ yarn inityarn init v1.12.3question name (graphql-typescript):question version (1.0.0):question description: for study
  • Vue入門-實戰教程
    Vue2+VueRouter2+Webpack+Axios 構建項目實戰(一)基礎知識概述:http://www.javazhiyin.com/?p=285Vue2+VueRouter2+Webpack+Axios 構建項目實戰(二)配置環境及構建初始項目:http://www.javazhiyin.com/?
  • 如何使用GraphQL-進階教程:客戶端
    在 Xcode 中,可以使用 助理編輯器(Assistant Editor) 來同時處理視圖控制器和 graphql 代碼。前端記事本,不定期更新,歡迎關注!
  • 【Vue.js入門到實戰教程】11-Vue Loader(下)| 編寫一個單文件 Vue 組件
    /App.vue'import 'bootstrap'import 'bootstrap/dist/css/bootstrap.min.css'...接下來,就可以正式編寫單文件組件了。編寫 ModalExample 組件我們將 vue_learning/component/slot.html 中的 modal-example 組件拆分出來,在 vue_learning/demo-project/src/components 目錄下新建一個單文件組件 ModalExample.vue,將 modal-example 組件代碼按照 Vue Loader 指定的格式填充到對應位置
  • 好課資源共享:mksz153 - Spark Streaming實時流處理項目實戰
    mksz134 - Spring Security技術棧開發企業級認證與授權mksz133 - 360大牛全面解讀PHP面試mksz132 - Google面試官親授 升級java面試mksz131 - Python前後端分離開發vue+Django REST framework實戰mksz128 - 10小時入門大數據hadoop
  • 新鮮出爐的一款SpringBoot +Vue的考試系統
    vue-router: Vue.js 官方的路由管理器。axios: 一個基於Promise 的HTTP 庫,可以用在瀏覽器和node.js 中。項目說明考試系統整體為前後端分離項目,作者又在這基礎上,將後端拆分成兩個管理員後端和學生考試後端,後端的代碼是在一起。
  • 一個後端狗的 Vue 筆記【入門級】
    B:安裝 vue-clicnpm install vue-cli -g安裝後在 npm 文件夾下打開 cmd 輸入 vue-list ,若出現下圖這種星狀內容,則完畢C:創建 vue-cli 程序自己在想要的位置創建一個目錄,選擇基於 webpack 的 vue 應用程式此文件夾下的 cmd 中輸入命令vue init webpack vue_02_myvue
  • Netflix 聯邦 GraphQL 平臺的實現過程及經驗教訓
    在這篇文章中,我們會將注意力轉移到成功運行聯邦 GraphQL 平臺所需的內容上——從它實現的過程到我們汲取的經驗教訓等方面。在過去的一年中,我們已經實現了聯邦 GraphQL 架構所需的核心基礎架構,正如我們前一篇文章所描述的那樣:
  • 實戰:在Node.js和Vue.js中構建文件壓縮應用程式
    前提要繼續學習本教程,你需要具備以下條件:熟悉HTML,CSS和Javascript(ES6 +)設置項目我們將從構建後端開始,這是我們應用程式的基礎。我們先編寫後端服務,所以在項目中再建立一個 server 目錄。
  • 2020年的前端工程師請收下這幾個Vue.js開源框架
    vue是一套用於構建用戶界面的漸進式JavaScript框架,簡單說Vue是類似於view的前端框架。vue開發核心是關注視圖層,同時它更加容易與第三方庫結合,再者我們在現有的項目中可以直接整合一起。目前vue技術社區在英文或中文都非常豐富,社區都有很多經驗豐富的開發人員,其功能也非常豐富與使用性,屬於輕量級框架。