前端從零開始學習Graphql

學習本姿式須要電腦裝有node,vue-cli相關環境,以及要有node,express,koa,vue相關基礎 css

本文相關demo的github地址: html

node服務:https://github.com/liuming888/graphql_node_demo.git
vue項目:https://github.com/liuming888/graphql_vue_demo.git 

一 Graphql概述

它是什麼?從哪裏來?要幹什麼?

簡單地講,對於前端,它就是讓你舒舒服服發請求的 前端

嚴格的說,它是一種api設計思想,用來取代restful api的一種前端處於主導地位的api規範。它把前端所須要的api用相似圖數據結構(graph)的方式展示出來,讓前端很方便的獲取所須要的數據。 vue

 

特色

須要什麼就獲取什麼數據 node

支持關係數據的查詢 react

API無需定義各類路由,徹底數據驅動 git

無需管理API版本,一個版本持續演進 github

支持大部分主流開發語言和平臺 web

強大的配套開發工具 sql

 

起源,restful api的問題

若是採用restful api的話,後端要持續維護api doc,可是實際場景是這樣的:

1.來了新需求,後端先評估和開發,後端弄得差很少,前端纔開始,而後中間後端一頓猛改

2.因爲多種緣由,後端常常本身偷偷改掉傳參或者返回值,完了接口報錯,測試姐姐把前端叫過去一頓批,前端一臉懵圈,仔細檢查,發現問題,找後端撕X,這樣一個循環很是影響開發效率。

gql出現

因爲上面這一堆的問題,facebook公司2012年內部實踐了GraphQL,15年剛開源的時候引發了不少大公司和社區關注,落地了不少規範和框架。須要瞭解詳細歷史能夠看看底下的youtube視頻。

這種叫GraphQL的東西幫助人們通過一系列定義和規範,能夠發送gql請求很是方便的拿到想要的數據,甚至還能夠修改數據,而不用後臺的配合,並且一旦Schema肯定(數據庫那邊定義好),先後端就能夠快速並行開發,例以下圖得到某個用戶的信息,我想要這個用戶的什麼屬性就寫什麼,graphiQl工具能夠進行完整的詳細的提示,請求主體簡單明瞭

 query{ 
 student{ 
 id 
 name 
 age 
 } 
 course{ 
 id 
 title 
 } 
 }

gql理想使用場景

數據庫建好模型,先後端能夠同步開始開發需求,前端只有少數須要依賴後端接口,前端開發過程當中能夠方便的拿到任何想要的數據,從而節省大量聯調接口的時間,迅速的完成一個項目。

實現原理

gql的實現挺複雜的,代碼有點難懂,不過原理提及來比較簡單易懂

var { graphql, buildSchema } = require('graphql'); 

 

// Construct a schema, using GraphQL schema language 
var schema = buildSchema(` 
type Query { 
 hello: String 
 } 
`); 

 

// The root provides a resolver functionfor each API endpoint 
var root = { 
 hello: () => { 
return'Hello world!'; 
 }, 
}; 

 

// Run the GraphQL query '{ hello }' and print out the response 
graphql(schema, '{ hello }', root).then((response) => { 
 console.log(response); 
});

如上就是一個最簡單的node版的gql服務器 gql把定義好的schema拿到,用root之類的resolve解析器去解析發送來的'{ hello }'請求,而後返回給相應的json值。上面代碼打印出的response以下

{ 
 data: 
 { 
 hello: 'Hello world!'
 } 
}

固然,resolve逐層解析會有一些問題,若是resolve請求數據庫,就要用到DataLoader

DataLoader是gql服務器性能的關鍵一環,也是gql社區的主要推進完善方向,就像react裏面的shouldComponentUpdate同樣制約着gql服務器的性能。

DataLoader 能讓咱們從數據庫讀取數據並讓數據能被 GraphQL 處理,咱們使用 DataLoader,而不是直接經過 SQL 查詢從數據庫獲取數據,將 DataLoader 做爲代理以減小咱們實際須要發送給數據庫的 SQL 查詢。 DataLoader 使用批處理和緩存的組合來實現。若是同一個客戶端請求會形成屢次請求數據庫,DataLoader 會整合這些問題並從數據庫批量拉取請求數據。DataLoader 會同時緩存這些數據,當有後續請求須要一樣資源時能夠直接從緩存獲取到。

具體使用

經過服務端對請求的元數據的type進行嚴格的定義,咱們只要在客戶端發送gql請求就能返回指望的相應類型的數據

下圖是請求格式,query【請求】,mutation【修改】和subscribe【訂閱】是三種api發送方式,query用的多一些,mutation相對傳統的restful來講不夠可靠和安全,subscribe相似websocket

query 
{ 
 schema { 
 types { 
 id 
 name // 獲取根字段名
 fields { 
 id 
 name // 獲取字段名
 } 
 } 
 } 
}

和restful比較的優缺點

優勢

優勢就是後端能夠少招幾個寫接口的

先後端一塊兒開發,節約工期

較少維護api文檔,節省精力

說了這些,其實單對於前端來講,幫助不算特別大

缺點和難推廣的地方

後端或者中間層把gql封裝相應業務對接數據庫是難點,須要高端人力

須要前端多少學一點類sql語句,不過大部分場景能夠封裝好固定的sql語句

封裝gql很差會產生sql性能問題,三級嵌套聯查還有n+1的老問題又會冒出來,須要持續優化

前端排除bug須要必定的後端知識,先後端架構多少了解一些

..

 

二 hello word

簡易版的hello world

npm install graphql

 

而後使用 node hello.js 以運行 hello.js 中的代碼:

 

var { graphql, buildSchema } = require('graphql');

 

var schema = buildSchema(`

type Query {

hello: String

}

`);

 

var root = { hello: () => 'Hello world!' };

 

graphql(schema, '{ hello }', root).then((response) => {

console.log(response);

});

 

控制檯打印出Hello world!

 

express版hello world

npm install express express-graphql graphql

 

而後使用 node server.js 以運行 server.js 中的代碼:

 

var express = require('express');

var graphqlHTTP = require('express-graphql');

var { buildSchema } = require('graphql');

 

var schema = buildSchema(`

type Query {

hello: String

}

`);

 

var root = { hello: () => 'Hello world!' };

 

var app = express();

app.use('/graphql', graphqlHTTP({

schema: schema,

rootValue: root,

graphiql: true, // 是否開啓調試模式(生產環境注意得關掉)

}));

app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));

 

訪問http://localhost:4000/graphql

 

 

koa版hello world

npm install koa graphql koa-graphql koa-mount

 

 

而後使用 node server.js 以運行 server.js 中的代碼:

const Koa = require('koa');

const mount = require('koa-mount');

const { buildSchema } = require('graphql');

const graphqlHTTP = require('koa-graphql');

const app = new Koa();

 

const schema = buildSchema(`

type Query {

hello: String

}

`);

 

const root = {

hello: () => 'Hello world!',

};

 

app.use(

mount(

'/graphql',

graphqlHTTP({

schema: schema,

rootValue: root,

graphiql: true,

})

)

);

 

app.use(async ctx => {

ctx.body = 'Hello World';

});

 

app.listen(3000);

 

訪問 http://localhost:3000/graphql

 

 

三 進階

參考資料:用Node建立GraphQL API:代碼量少,性能高

 

基於express

代碼看 node-graphql-demo項目 而後yarn npm run dev

github地址: https://github.com/liuming888/graphql_node_demo.git master分支

建立 Project

咱們如今來建立咱們的 project。打開一個新的 terminal,執行如下命令,便可使用缺省值建立 package.json 文件:

mkdir node-graphql-demo

cd node-graphql-demo

npm init -y

 

接下來,咱們須要安裝如下依賴:

npm install graphql express express-graphql sqlite3 --save

這樣就會安裝 Express 框架,Node.js 上的 GraphQL 實現,Express 和 SQLite3 的 GraphQL 中間件。爲簡單起見,咱們使用 SQLite3 做爲數據庫。

 

graphql 是一個支持庫,而且在咱們這裏是一個必要的模塊

 

express 是一個簡潔而靈活的 node.js Web應用框架, 提供了一系列強大特性幫助你建立各類 Web 應用,和豐富的 HTTP 工具。

 

express-graphql GraphQL HTTP服務器中間件

 

SQLite是一個進程內的庫,實現了自給自足的、無服務器的、零配置的、事務性的 SQL 數據庫引擎。它是一個零配置的數據庫,這意味着與其餘數據庫同樣,您不須要在系統中配置。就像其餘數據庫,SQLite 引擎不是一個獨立的進程,能夠按應用程序需求進行靜態或動態鏈接。SQLite 直接訪問其存儲文件

 

建立 GraphQL 服務器

建立工程並引入基本依賴包以後,如今來建立 API 服務器。在工程文件夾裏,建立 index.js 文件,並引入下列內容:

const express = require('express');

const sqlite3 = require('sqlite3').verbose();

const graphql = require("graphql");

const ExpressGraphQL = require("express-graphql");

上面的代碼的目的是:爲Express導入Express,SQLite3,GraphQL和GraphQL中間件。

 

接下來,添加下列代碼,在當前文件夾中建立一個 Express 應用程序和名爲 my.db 的 SQLite 3 數據庫:

const app = express();

const database = new sqlite3.Database("./my.db");

 

而後,添加 createContactTable() 方法,在數據庫中建立 contacts 表並立刻調用函數:

const createContactTable = () => {

const query = `

CREATE TABLE IF NOT EXISTS contacts (

id integer PRIMARY KEY,

firstName text,

lastName text,

email text UNIQUE)`;

return database.run(query);

}

createContactTable();

咱們建立了一個 SQL 表來存儲 contacts的基本信息。每一個 contact 的基本信息包括:惟一的標識、名、姓和 email。

 

接下來,添加下列代碼來定義一個 GraphQL 類型:

const ContactType = new graphql.GraphQLObjectType({

name: "Contact",

fields: {

id: { type: graphql.GraphQLID },

firstName: { type: graphql.GraphQLString },

lastName: { type: graphql.GraphQLString },

email: { type: graphql.GraphQLString }

}

});

咱們使用基本的內置 GraphQL 類型,如 GraphQLID 和 GraphQLString 來建立咱們自定義類型,對應數據庫中的 contact。

相關連接:

GraphQLID: https://graphql.github.io/graphql-spec/draft/#sec-ID

GraphQLString: https://graphql.github.io/graphql-spec/draft/#sec-String

 

接着,定義查詢類型,以下所示:

var queryType = new graphql.GraphQLObjectType({

name: 'Query',

fields: {

contacts: {

type: graphql.GraphQLList(ContactType),

resolve: (root, args, context, info) => {

return new Promise((resolve, reject) => {

database.all("SELECT * FROM contacts;", function (err, rows) {

if (err) {

reject([]);

}

resolve(rows);

});

});

 

}

},

contact: {

type: ContactType,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

}

},

resolve: (root, {

id

}, context, info) => {

return new Promise((resolve, reject) => {

 

database.all("SELECT * FROM contacts WHERE id = (?);", [id], function (err, rows) {

if (err) {

reject(null);

}

resolve(rows[0]);

});

});

}

}

}

});

咱們的查詢有兩個字段: contacts,能夠用來獲取數據庫中的全部 contacts,而 contact 則根據 id 獲取一個 contact 信息。 contact 字段容許所需的 id 參數爲 GraphQLID 類型。

每一個字段都有一個 type,用來講明返回數據的類型,args 定義指望從客戶端獲得的參數, resolve 則定義了在獲取數據邏輯中實際使用的方法。

對於前兩個字段, resolve() 方法是實際邏輯發生的地方—— 咱們簡單調用 database.all() 和 database.run() 方法來執行正確的 SQL 查詢,以便從 SQLite 獲取數據,返回一個 Promise 來處理獲得的數據。

咱們能夠從resolve()方法的第二個參數訪問任何傳遞的參數。

接下來,咱們建立一個 mutation 類型,用於建立、更新和刪除操做: https://graphql.github.io/graphql-spec/draft/#sec-Mutation

var mutationType = new graphql.GraphQLObjectType({

name: 'Mutation',

fields: {

createContact: {

type: ContactType,

args: {

firstName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

lastName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

email: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

}

},

resolve: (root, {

firstName,

lastName,

email

}) => {

return new Promise((resolve, reject) => {

database.run('INSERT INTO contacts (firstName, lastName, email) VALUES (?,?,?);', [firstName, lastName, email], (err) => {

if (err) {

reject(null);

}

database.get("SELECT last_insert_rowid() as id", (err, row) => {

 

resolve({

id: row["id"],

firstName: firstName,

lastName: lastName,

email: email

});

});

});

})

 

}

},

updateContact: {

type: graphql.GraphQLString,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

},

firstName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

lastName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

email: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

}

},

resolve: (root, {

id,

firstName,

lastName,

email

}) => {

return new Promise((resolve, reject) => {

database.run('UPDATE contacts SET firstName = (?), lastName = (?), email = (?) WHERE id = (?);', [firstName, lastName, email, id], (err) => {

if (err) {

reject(err);

}

resolve(`Contact #${id} updated`);

 

});

})

}

},

deleteContact: {

type: graphql.GraphQLString,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

}

},

resolve: (root, {

id

}) => {

return new Promise((resolve, reject) => {

database.run('DELETE from contacts WHERE id =(?);', [id], (err) => {

if (err) {

reject(err);

}

resolve(`Contact #${id} deleted`);

 

});

})

 

}

}

}

});

咱們的 mutation 類型有三個字段:

createContact 建立 contacts;

updateContact 更新 contacts;

deleteContact 刪除 contacts.

全部的字段都接受符合 args 屬性定義的參數,並由一個 resolve() 方法來獲取傳遞過來的參數,執行相應的 SQL 操做,並返回一個 Promise。

而後,建立 GraphQL schema: https://graphql.github.io/graphql-spec/draft/#sec-Schema

const schema = new graphql.GraphQLSchema({

query: queryType,

mutation: mutationType

});

GraphQL schema 是 GraphQL 的核心概念,它定義了鏈接到服務器的客戶端可用的功能。咱們傳遞已定義的 query 和 mutation 類型到 schema。

最後,掛載到 /graphql 端點,在 4000 端口運行 Express 服務器:

app.use("/graphql", ExpressGraphQL({ schema: schema, graphiql: true}));

app.listen(4000, () => {

console.log("GraphQL server running at http://localhost:4000.");

});

保存 index.js 文件,返回到你的 terminal。運行下列命令,啓動服務器:

node index.js

 

 

怎樣使用 GraphQL API

構建客戶端前,可使用 GraphQL 接口來測試你的 API。

訪問網址 : http://localhost:4000/graphql ,並運行下列 mutation query:

mutation {

createContact(firstName: "Jon", lastName: "Snow", email: "jonsnow@thenightswatch.com") {

id,

firstName,

lastName,

email

}

}

 

使用下列 mutation,能夠更新 id 爲 1 的 contact:

mutation {

updateContact(id: 1, firstName: "Aegon", lastName: "Targaryen", email: "aegontargaryen@ironthrone.com")

}

 

也可使用下列 mutation 來刪除 id 爲 1 的 contact:

mutation {

deleteContact(id: 1)

}

 

最後,使用以下 query,能夠獲取數據庫中全部 contacts 信息:

query {

contacts {

id

firstName

lastName

email

}

}

 

使用下面 query,能夠獲取一個 contact 的信息:

query {

contact(id: 1) {

id

firstName

lastName

email

}

}

 

這裏,咱們獲得的是 id 爲 1 的 contact 信息。

 

 

基於koa (和express相似)

 

代碼看 koa-demo項目 而後yarn npm run dev

github地址: https://github.com/liuming888/graphql_node_demo.git koa分支

 

 

建立 Project

咱們如今來建立咱們的 project。打開一個新的 terminal,執行如下命令,便可使用缺省值建立 package.json 文件:

mkdir koa-demo

cd koa-demo

npm init -y

 

接下來,咱們須要安裝如下依賴:

npm install graphql koa koa-graphql koa-mount sqlite3 --save

這樣就會安裝 koa庫,Node.js 上的 GraphQL 實現,koa 和 SQLite3 的 GraphQL 中間件。爲簡單起見,咱們使用 SQLite3 做爲數據庫。

 

graphql 是一個支持庫,而且在咱們這裏是一個必要的模塊

 

Koa 是一個新的 web 框架,由 Express 幕後的原班人馬打造, 致力於成爲 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。 經過利用 async 函數,Koa 幫你丟棄回調函數,並有力地加強錯誤處理。 Koa 並無捆綁任何中間件, 而是提供了一套優雅的方法,幫助您快速而愉快地編寫服務端應用程序

 

koa-graphql GraphQL HTTP服務器中間件

 

koa-mount 讓多個Koa.js子應用合併成一個父應用,用請求的前綴區分子應用

 

SQLite是一個進程內的庫,實現了自給自足的、無服務器的、零配置的、事務性的 SQL 數據庫引擎。它是一個零配置的數據庫,這意味着與其餘數據庫同樣,您不須要在系統中配置。就像其餘數據庫,SQLite 引擎不是一個獨立的進程,能夠按應用程序需求進行靜態或動態鏈接。SQLite 直接訪問其存儲文件

 

建立 GraphQL 服務器

建立工程並引入基本依賴包以後,如今來建立 API 服務器。在工程文件夾裏,建立 index.js 文件,並引入下列內容:

const Koa = require('koa');

const mount = require('koa-mount');

const graphql = require('graphql');

const graphqlHTTP = require('koa-graphql');

const sqlite3 = require('sqlite3').verbose();

const app = new Koa();

 

const database = new sqlite3.Database('./my.db');

const createContactTable = () => {

const query = `

CREATE TABLE IF NOT EXISTS contacts (

id integer PRIMARY KEY,

firstName text,

lastName text,

email text UNIQUE)`;

return database.run(query);

};

// 建立了一個 SQL 表來存儲 contacts的基本信息。每一個 contact 的基本信息包括:惟一的標識、名、姓和 email。

createContactTable();

 

// 定義一個 GraphQL 類型

// 使用基本的內置 GraphQL 類型,如 GraphQLID 和 GraphQLString 來建立咱們自定義類型,對應數據庫中的 contact。

const ContactType = new graphql.GraphQLObjectType({

name: 'Contact',

fields: {

id: { type: graphql.GraphQLID },

firstName: { type: graphql.GraphQLString },

lastName: { type: graphql.GraphQLString },

email: { type: graphql.GraphQLString },

},

});

 

// 定義查詢類型

// 查詢有兩個字段: contacts,能夠用來獲取數據庫中的全部 contacts,而 contact 則根據 id 獲取一個 contact 信息。 contact 字段容許所需的 id 參數爲 GraphQLID 類型。

var queryType = new graphql.GraphQLObjectType({

name: 'Query',

fields: {

contacts: {

type: graphql.GraphQLList(ContactType),

resolve: (root, args, context, info) => {

return new Promise((resolve, reject) => {

database.all("SELECT * FROM contacts;", function (err, rows) {

if (err) {

reject([]);

}

resolve(rows);

});

});

 

}

},

contact: {

type: ContactType, // 說明返回數據的類型

args: { // 定義指望從客戶端獲得的參數

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

}

},

resolve: (root, { // 實際邏輯發生的地方

id // resolve()方法的第二個參數訪問任何傳遞的參數

}, context, info) => {

// 簡單調用 database.all() 和 database.run() 方法來執行正確的 SQL 查詢,以便從 SQLite 獲取數據,返回一個 Promise 來處理獲得的數據。

return new Promise((resolve, reject) => {

 

database.all("SELECT * FROM contacts WHERE id = (?);", [id], function (err, rows) {

if (err) {

reject(null);

}

resolve(rows[0]);

});

});

}

}

}

});

 

// 建立一個 mutation 類型,用於建立、更新和刪除操做

var mutationType = new graphql.GraphQLObjectType({

name: 'Mutation',

fields: { // 全部的字段都接受符合 args 屬性定義的參數,並由一個 resolve() 方法來獲取傳遞過來的參數,執行相應的 SQL 操做,並返回一個 Promise。

createContact: { // 建立 contacts

type: ContactType,

args: {

firstName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

lastName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

email: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

}

},

resolve: (root, {

firstName,

lastName,

email

}) => {

return new Promise((resolve, reject) => {

database.run('INSERT INTO contacts (firstName, lastName, email) VALUES (?,?,?);', [firstName, lastName, email], (err) => {

if (err) {

reject(null);

}

database.get("SELECT last_insert_rowid() as id", (err, row) => {

 

resolve({

id: row["id"],

firstName: firstName,

lastName: lastName,

email: email

});

});

});

})

 

}

},

updateContact: { // 更新 contacts

type: graphql.GraphQLString,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

},

firstName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

lastName: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

},

email: {

type: new graphql.GraphQLNonNull(graphql.GraphQLString)

}

},

resolve: (root, {

id,

firstName,

lastName,

email

}) => {

return new Promise((resolve, reject) => {

database.run('UPDATE contacts SET firstName = (?), lastName = (?), email = (?) WHERE id = (?);', [firstName, lastName, email, id], (err) => {

if (err) {

reject(err);

}

resolve(`Contact #${id} updated`);

 

});

})

}

},

deleteContact: { // 刪除 contacts

type: graphql.GraphQLString,

args: {

id: {

type: new graphql.GraphQLNonNull(graphql.GraphQLID)

}

},

resolve: (root, {

id

}) => {

return new Promise((resolve, reject) => {

database.run('DELETE from contacts WHERE id =(?);', [id], (err) => {

if (err) {

reject(err);

}

resolve(`Contact #${id} deleted`);

 

});

})

 

}

}

}

});

 

 

// 建立 GraphQL schema

// GraphQL schema 是 GraphQL 的核心概念,它定義了鏈接到服務器的客戶端可用的功能。咱們傳遞已定義的 query 和 mutation 類型到 schema。

const schema = new graphql.GraphQLSchema({

query: queryType,

mutation: mutationType

});

 

app.use(

mount(

'/graphql',

graphqlHTTP({

schema: schema,

graphiql: true, //是否開啓本地調試模式

})

)

);

 

app.use(async ctx => {

ctx.body = 'Hello World';

});

 

app.listen(3000,() => {

console.log("GraphQL server running at http://localhost:3000");

});

 

保存 index.js 文件,返回到你的 terminal。運行下列命令,啓動服務器:

node index.js

 

 

怎樣使用 GraphQL API

構建客戶端前,可使用 GraphQL 接口來測試你的 API。

訪問網址 : http://localhost:4000/graphql ,並運行下列 mutation query:

mutation {

createContact(firstName: "Jon", lastName: "Snow", email: "jonsnow@thenightswatch.com") {

id,

firstName,

lastName,

email

}

}

 

使用下列 mutation,能夠更新 id 爲 1 的 contact:

mutation {

updateContact(id: 1, firstName: "Aegon", lastName: "Targaryen", email: "aegontargaryen@ironthrone.com")

}

 

也可使用下列 mutation 來刪除 id 爲 1 的 contact:

mutation {

deleteContact(id: 1)

}

 

最後,使用以下 query,能夠獲取數據庫中全部 contacts 信息:

query {

contacts {

id

firstName

lastName

email

}

}

 

使用下面 query,能夠獲取一個 contact 的信息:

query {

contact(id: 1) {

id

firstName

lastName

email

}

}

 

這裏,咱們獲得的是 id 爲 1 的 contact 信息。

 

 

 

 

四 Vue和GraphQL

參考資料 使用Vue和GraphQL構建一個CRUD APP (改資料的修改有問題,本文已修復)

 

相關代碼:

node服務: https://github.com/liuming888/graphql_node_demo.git
vue項目: https://github.com/liuming888/graphql_vue_demo.git 

node服務複用上文進階中的項目(下面採用koa服務)

打開koa-demo項目的server.js

增長一下代碼

const cors = require('@koa/cors');

...

app.use(cors());

 

而後yarn add @koa/cors 再npm run dev

由於咱們在兩個不一樣的本地端口之間發送請求,這兩個端口被視爲兩個獨立的域,所以咱們須要在服務器中啓用跨源資源共享功能

 

建立一個 Vue 項目

使用 Vue CLI,讓咱們繼續建立一個 Vue 項目。回到終端機,執行下列命令:

vue create vue-graphql-demo

當提示對預置選項進行選擇時,你能夠簡單地選擇默認設置。

等待生成項目,運行如下命令啓動開發服務器:

cd vue-graphql-demo

npm run serve

這樣,你的應用就運行起來了,在 http://localhost:8080/ 地址下能夠訪問。

 

安裝 Apollo 客戶端

Apollo 是一組實用程序,能夠幫助你在 APP 中使用 GraphQL。它以其客戶端和服務器而聞名。Apollo 是由 Meteor Development Group開發和維護的:https://www.meteor.io/

打開一個新的終端,進入你的項目文件夾,運行如下命令來安裝 Apollo 客戶端到你的 Vue 項目中:

yarn add vue-apollo graphql apollo-boost

Apollo Boost 是一種無需任何手動配置就能夠開始使用 Apollo 客戶端的方式。它包括一些常見的默認值,好比推薦的 InMemoryCache 和 HttpLink,它們是以推薦的參數爲你進行配置的。這種零配置的方式適合快速開發使用。

 

修改src/main.js 文件,代碼以下:

import Vue from 'vue';

import ApolloClient from 'apollo-boost';

import VueApollo from 'vue-apollo';

import App from './App.vue';

import router from './router';

import store from './store';

import './registerServiceWorker';

 

// 建立 Apollo 客戶端的一個實例,並傳入 GraphQL 端點的 URL

const apolloClient = new ApolloClient({

uri: 'http://localhost:3000/graphql', // graphql服務的地址 (注意服務端開啓cors)

});

 

Vue.use(VueApollo); // 使用 VueApollo 插件將 Apollo 集成到咱們的 Vue APP 中

 

// 建立了 Apollo Provider,它包含全部 Vue 組件均可以使用的 Apollo Client 實例。

const apolloProvider = new VueApollo({

defaultClient: apolloClient,

});

 

Vue.config.productionTip = false;

 

new Vue({

router,

store,

render: h => h(App),

apolloProvider, // 將 Apollo Provider 添加到 Vue 實例中

}).$mount('#app');

 

使用 GraphQL API

將 vue-apollo 添加到 APP 後,全部組件均可以經過 apollo 選項來使用 Apollo

 

修改App.vue代碼以下:

<template>

<div id="app">

<table border='1'

width='100%'

style='border-collapse: collapse;'>

<tr>

<th>First Name</th>

<th>Last Name</th>

<th>Email</th>

<th>Actions</th>

</tr>

 

<tr v-for='contact in contacts'

:key="contact.id">

<td>{{ contact.firstName }}</td>

<td>{{ contact.lastName }}</td>

<td>{{ contact.email }}</td>

<td>

<input type="button"

@click="selectContact(contact)"

value="Select">

<input type="button"

@click="deleteContact(contact.id)"

value="Delete">

</td>

</tr>

</table>

 

</br>

<form>

<label>First Name</label>

<input type="text"

name="firstName"

v-model="firstName">

</br>

 

<label>Last Name</label>

<input type="text"

name="lastName"

v-model="lastName">

</br>

 

<label>Email</label>

<input type="email"

name="email"

v-model="email">

</br>

 

<input v-if="!id"

type="button"

@click="createContact(firstName, lastName, email)"

value="Add">

<input v-if="id"

type="button"

@click="updateContact(id, firstName, lastName, email)"

value="Update">

<input type="button"

@click="clearForm()"

value="Clear">

 

</form>

</div>

</template>

 

<script>

import gql from "graphql-tag";

export default {

data() {

// 定義了四個組件變量,分別是 id、firstName、lastName 和 email。這些變量將被綁定到用於建立新聯繫人的 HTML Form 中

return {

id: null,

firstName: "",

lastName: "",

email: ""

};

},

apollo: {

// gql 是一個 JavaScript 模板文字標籤,它將 GraphQL 查詢字符串解析到標準的 GraphQL AST 中。能夠在 Github 上的官方庫中找到更多詳細信息:

// https://github.com/apollographql/graphql-tag

 

// 在這個 apollo 對象中,咱們添加了一個 contacts 屬性,它將保存 contacts 查詢的結果。稍後,咱們將使用它在模板中顯示聯繫人。

contacts: gql`

query {

contacts {

id

firstName

lastName

email

}

}

`

},

methods: {

// 添加 createContact()、updateContact() 和 deleteContact()

// 使用 this.$apollo.mutate() 方法向 GraphQL Server 發送 mutation 查詢,並調用 location.reload() 方法來從新加載頁面。

 

createContact(firstName, lastName, email) {

console.log(`Create contact: ${email}`);

this.$apollo.mutate({

mutation: gql`

mutation createContact(

$firstName: String!

$lastName: String!

$email: String!

) {

createContact(

firstName: $firstName

lastName: $lastName

email: $email

) {

id

firstName

lastName

email

}

}

`,

variables: {

firstName: firstName,

lastName: lastName,

email: email

}

});

location.reload();

},

updateContact(id, firstName, lastName, email) {

console.log(

"id, firstName, lastName, email: ",

id,

firstName,

lastName,

email

);

console.log(`Update contact: # ${id}`);

this.$apollo.mutate({

mutation: gql`

mutation updateContact(

$id: ID!

$firstName: String!

$lastName: String!

$email: String!

) {

updateContact(

id: $id

firstName: $firstName

lastName: $lastName

email: $email

)

}

`,

variables: {

id: id,

firstName: firstName,

lastName: lastName,

email: email

}

});

location.reload();

},

deleteContact(id) {

console.log(`Delete contact: # ${id}`);

this.$apollo.mutate({

mutation: gql`

mutation deleteContact($id: ID!) {

deleteContact(id: $id)

}

`,

variables: {

id: id

}

});

location.reload();

},

selectContact(contact) {

this.id = contact.id;

this.firstName = contact.firstName;

this.lastName = contact.lastName;

this.email = contact.email;

},

clearForm() {

this.id = null;

this.firstName = "";

this.lastName = "";

this.email = "";

}

}

};

</script>

 

 

 

<style lang="scss">

</style>

 

 

保存後,從新啓動vue項目,會看到如下頁面:

 

到此,恭喜你已經成功入門graphql了,有興趣能夠自行查詢相關資料深刻研究

 

五 總結

使用GraphQL API的目的是什麼?

建立API的目的是使本身的軟件具備能夠被其餘外部服務集成的能力。即便你的程序被單個前端程序所使用,也能夠將此前端視爲外部服務,爲此,當經過API爲二者之間提供通訊時,你可以在不一樣的項目中工做。

若是你在一個大型團隊中工做,能夠將其拆分爲建立前端和後端團隊,從而容許他們使用相同的技術,並使他們的工做更輕鬆。

優勢

優勢就是後端能夠少招幾個寫接口的

先後端一塊兒開發,節約工期

較少維護api文檔,節省精力(代碼便是文檔)

說了這些,其實單對於前端來講,幫助不算特別大

缺點和難推廣的地方

後端或者中間層把gql封裝相應業務對接數據庫是難點,須要高端人力

須要前端多少學一點類sql語句,不過大部分場景能夠封裝好固定的sql語句

封裝gql很差會產生sql性能問題,三級嵌套聯查還有n+1的老問題又會冒出來,須要持續優化

前端排除bug須要必定的後端知識,先後端架構多少了解一些

 

GraphQL比REST更好嗎?

GraphQL是一種適合多種狀況的方法。 REST是一種體系結構方法。現在,有大量的文章能夠解釋爲何一個比另外一個好,或者爲何你應該只使用REST而不是GraphQL。另外你能夠經過多種方式在內部使用GraphQL,並將API的端點維護爲基於REST的架構。

你應該作的是瞭解每種方法的好處,分析本身正在建立的解決方案,評估你的團隊使用解決方案的溫馨程度,並評估你是否可以指導你的團隊快速掌握這些技術。

本文更偏重於實用指南,而不是GraphQL和REST的主觀比較。若是你想查看這二者的詳細比較,我建議你查看另外一篇文章,爲何GraphQL是API的將來

 

參考資料

graphql官網

2019 GraphQL學習指南    

使用 NodeJS 建立一個 GraphQL 服務器

用Node建立GraphQL API:代碼量少,性能高

用Node.js建立安全的 GraphQL API[每日前端夜話0x61]

Koa + GraphQL 示例

GraphQL 入門詳解

使用Vue和GraphQL構建一個CRUD APP

相關文章
相關標籤/搜索