翻譯:瘋狂的技術宅 www.toptal.com/graphql/gra…前端
本文的目標是提供關於如何建立安全的 Node.js GraphQL API 的快速指南。node
你可能會想到一些問題:git
這些問題都是有意義的,但在回答以前,咱們應該深刻了解當前 Web 開發的狀態:github
你會發現幾乎在每種狀況下都會有一個不須要你去詳細瞭解的API,例如你不須要知道它們是怎樣構建的,而且不須要使用與他們相同的技術就可以將其集成到你本身的系統中。API容許你提供一種能夠在服務器和客戶端通訊之間進行通用標準通訊的方式,而沒必要依賴於特定的技術棧。typescript
經過結構良好的API,能夠擁有可靠、可維護且可擴展的API,能夠爲多種客戶端和前端應用提供服務。數據庫
GraphQL 是一種 API 所使用的查詢語言,由Facebook開發並用於其內部項目,並於2015年公開發布。它支持讀取、寫入和實時更新等操做。同時它也是開源的,一般會與REST和其餘架構放在一塊兒進行比較。簡而言之,它基於:express
雖然本文應該展現一個關於如何構建和使用GraphQL API的簡單但真實的場景,但咱們不會去詳細介紹GraphQL。由於GraphQL團隊提供了全面的文檔,並在Introduction to GraphQL中列出了幾個最佳實踐。npm
如上所述,查詢是客戶端從API讀取和操做數據的一種方式。你能夠傳遞對象的類型,並選擇要接收的字段類型。下面是一個簡單的查詢:編程
query{
users{
firstName,
lastName
}
}
複製代碼
咱們嘗試從用戶庫中查詢全部用戶,但只接收firstName
和lastName
。此查詢的結果將相似於:json
{
"data": {
"users": [
{
"firstName": "Marcos",
"lastName": "Silva"
},
{
"firstName": "Paulo",
"lastName": "Silva"
}
]
}
}
複製代碼
客戶端的使用很是簡單。
建立API的目的是使本身的軟件具備能夠被其餘外部服務集成的能力。即便你的程序被單個前端程序所使用,也能夠將此前端視爲外部服務,爲此,當經過API爲二者之間提供通訊時,你可以在不一樣的項目中工做。
若是你在一個大型團隊中工做,能夠將其拆分爲建立前端和後端團隊,從而容許他們使用相同的技術,並使他們的工做更輕鬆。
在本文中,咱們將重點介紹怎樣構建使用GraphQL API的框架。
GraphQL是一種適合多種狀況的方法。 REST是一種體系結構方法。現在,有大量的文章能夠解釋爲何一個比另外一個好,或者爲何你應該只使用REST而不是GraphQL。另外你能夠經過多種方式在內部使用GraphQL,並將API的端點維護爲基於REST的架構。
你應該作的是瞭解每種方法的好處,分析本身正在建立的解決方案,評估你的團隊使用解決方案的溫馨程度,並評估你是否可以指導你的團隊快速掌握這些技術。
本文更偏重於實用指南,而不是GraphQL和REST的主觀比較。若是你想查看這二者的詳細比較,我建議你查看咱們的另外一篇文章,爲何GraphQL是API的將來。
在今天的文章中,咱們將專一於怎樣用Node.js建立GraphQL API。
GraphQL有好幾個不一樣的支持庫可供使用。出於本文的目的,咱們決定使用Node.js環境下的庫,由於它的應用很是普遍,而且Node.js容許開發人員使用他們熟悉的前端語法進行服務器端開發。
咱們將爲本身的 GraphQL API 設計一個構思的框架,在開始以前,你須要瞭解Node.js和Express的基礎知識。這個GraphQL示例項目的源代碼能夠在這裏找到(github.com/makinhs/nod…
咱們將會處理兩種類型的資源:
Users 包含如下字段:
Products 包含如下字段:
至於編碼標準,咱們將在這個項目中使用TypeScript。
首先,要確保安裝了最新的Node.js版本。在本文發佈時,在Nodejs.org上當前版本爲10.15.3。
讓咱們建立一個名爲node-graphql
的新文件夾,並在終端或Git CLI控制檯下使用如下命令:npm init
。
爲了節約時間,在咱們的Git存儲庫中找到如下代碼去替換你的package.json
應該包含的依賴項:
{
"name": "node-graphql",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"tsc": "tsc",
"start": "npm run tsc && node ./build/app.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/express": "^4.16.1",
"@types/express-graphql": "^0.6.2",
"@types/graphql": "^14.0.7",
"express": "^4.16.4",
"express-graphql": "^0.7.1",
"graphql": "^14.1.1",
"graphql-tools": "^4.0.4"
},
"devDependencies": {
"tslint": "^5.14.0",
"typescript": "^3.3.4000"
}
}
複製代碼
更新package.json
後,在終端中執行:npm install
。
接着是配置咱們的TypeScript模式。在根文件夾中建立一個名爲tsconfig.json
的文件,其中包含如下內容:
{
"compilerOptions": {
"target": "ES2016",
"module": "commonjs",
"outDir": "./build",
"strict": true,
"esModuleInterop": true
}
}
複製代碼
這個配置的代碼邏輯將會出如今app文件夾中。在那裏咱們能夠建立一個app.ts
文件,在裏面添加如下代碼用於基本測試:
console.log('Hello Graphql Node API tutorial');
複製代碼
經過前面的配置,如今咱們能夠運行 npm start
進行構建和測試了。在終端控制檯中,你應該可以看到輸出的字符串「Hello Graphql Node API tutorial」。在後臺場景中,咱們的配置會將 TypeScript 代碼編譯爲純 JavaScript,而後在build
文件夾中執行構建。
如今爲GraphQL API配置一個基本框架。爲了開始咱們的項目,將添加三個基本的導入:
把它們放在一塊兒:
import express from 'express';
import graphqlHTTP from 'express-graphql';
import {makeExecutableSchema} from 'graphql-tools';
複製代碼
如今應該可以開始編碼了。下一步是在Express中處理咱們的程序和基本的GraphQL配置,例如:
import express from 'express';
import graphqlHTTP from 'express-graphql';
import {makeExecutableSchema} from 'graphql-tools';
const app: express.Application = express();
const port = 3000;
let typeDefs: any = [` type Query { hello: String } type Mutation { hello(message: String) : String } `];
let helloMessage: String = 'World!';
let resolvers = {
Query: {
hello: () => helloMessage
},
Mutation: {
hello: (_: any, helloData: any) => {
helloMessage = helloData.message;
return helloMessage;
}
}
};
app.use(
'/graphql',
graphqlHTTP({
schema: makeExecutableSchema({typeDefs, resolvers}),
graphiql: true
})
);
app.listen(port, () => console.log(`Node Graphql API listening on port ${port}!`));
複製代碼
咱們正在作的是:
好的,可是typeDefs和resolvers中發生了什麼,它們與查詢和修改的關係又是怎樣的呢?
如今讓咱們再次運行npm start,看看咱們能獲得些什麼。咱們但願該程序運行後產生這種效果:Graphql API 偵聽3000端口。
咱們如今能夠試着經過訪問 http://localhost:3000/graphql 查詢和測試GraphQL API:
好了,如今能夠編寫第一個本身的查詢了,先定義爲「hello」。
請注意,咱們在typeDefs
中定義它的方式,頁面能夠幫助咱們構建查詢。
這很好,但咱們怎樣才能改變值呢?固然是mutation!
如今,讓咱們看看當咱們用mutation對值進行改變時會發生什麼:
如今咱們能夠用GraphQL Node.js API進行基本的CRUD操做了。接下來開始使用這些代碼。
對於Products,咱們將使用名爲products的模塊。爲了是本文不那麼囉嗦,咱們將用內存數據庫進行演示。先定義一個模型和服務來管理Products。
咱們的模型將基於如下內容:
export class Product {
private id: Number = 0;
private name: String = '';
private description: String = '';
private price: Number = 0;
constructor(productId: Number, productName: String, productDescription: String, price: Number) {
this.id = productId;
this.name = productName;
this.description = productDescription;
this.price = price;
}
}
複製代碼
與GraphQL通訊的服務定義爲:
export class ProductsService {
public products: any = [];
configTypeDefs() {
let typeDefs = ` type Product { name: String, description: String, id: Int, price: Int } `;
typeDefs += ` extend type Query { products: [Product] } `;
typeDefs += ` extend type Mutation { product(name:String, id:Int, description: String, price: Int): Product! }`;
return typeDefs;
}
configResolvers(resolvers: any) {
resolvers.Query.products = () => {
return this.products;
};
resolvers.Mutation.product = (_: any, product: any) => {
this.products.push(product);
return product;
};
}
}
複製代碼
對於users,咱們將遵循與products模塊相同的結構。咱們將爲用戶提供模型和服務。該模型將定義爲:
export class User {
private id: Number = 0;
private firstName: String = '';
private lastName: String = '';
private email: String = '';
private password: String = '';
private permissionLevel: Number = 1;
constructor(id: Number, firstName: String, lastName: String, email: String, password: String, permissionLevel: Number) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
this.permissionLevel = permissionLevel;
}
}
複製代碼
同時,咱們的服務將會是這樣:
const crypto = require('crypto');
export class UsersService {
public users: any = [];
configTypeDefs() {
let typeDefs = ` type User { firstName: String, lastName: String, id: Int, password: String, permissionLevel: Int, email: String } `;
typeDefs += ` extend type Query { users: [User] } `;
typeDefs += ` extend type Mutation { user(firstName:String, lastName: String, password: String, permissionLevel: Int, email: String, id:Int): User! }`;
return typeDefs;
}
configResolvers(resolvers: any) {
resolvers.Query.users = () => {
return this.users;
};
resolvers.Mutation.user = (_: any, user: any) => {
let salt = crypto.randomBytes(16).toString('base64');
let hash = crypto.createHmac('sha512', salt).update(user.password).digest("base64");
user.password = hash;
this.users.push(user);
return user;
};
}
}
複製代碼
提醒一下,源代碼能夠在 github.com/makinhs/nod… 找到。
如今運行並測試咱們的代碼。運行npm start
,將在端口3000上運行服務器。咱們如今能夠經過訪問http://localhost:3000/graphql來測試本身的GraphQL
嘗試一個mutation,將一個項目添加到咱們的product列表中:
爲了測試它是否有效,咱們如今使用查詢,但只接收id
,name
和price
:
query{
products{
id,
name,
price
}
}
將會返回:
{
"data": {
"products": [
{
"id": 100,
"name": "My amazing product",
"price": 400
}
]
}
}
複製代碼
很好,按照預期工做了。如今能夠根據須要獲取字段了。你能夠試着添加一些描述:
query{
products{
id,
name,
description,
price
}
}
複製代碼
如今咱們能夠對product進行描述。接下來試試user吧。
mutation{
user(id:200,
firstName:"Marcos",
lastName:"Silva",
password:"amaz1ingP4ss",
permissionLevel:9,
email:"marcos.henrique@toptal.com") {
id
}
}
複製代碼
查詢以下:
query{
users{
id,
firstName,
lastName,
password,
email
}
}
複製代碼
返回內容以下:
{
"data": {
"users": [
{
"id": 200,
"firstName": "Marcos",
"lastName": "Silva",
"password": "kpj6Mq0tGChGbZ+BT9Nw6RMCLReZEPPyBCaUS3X23lZwCCp1Ogb94/oqJlya0xOBdgEbUwqRSuZRjZGhCzLdeQ==",
"email": "marcos.henrique@toptal.com"
}
]
}
}
複製代碼
到此爲止,咱們的GraphQL骨架完成!雖然離實現一個有用的、功能齊全的API還須要不少步驟,但如今已經設置好了基本的核心功能。
讓咱們回顧一下本文的內容:
爲了集中精力關注GraphQL API自己,咱們忽略了幾個重要的步驟,可簡要總結以下:
請記住,咱們在Git (github.com/makinhs/nod… 並運行它!請注意,本文中提出的全部標準和建議並非一成不變的。
這只是設計GraphQL API的衆多方法之一。此外,請務必更詳細地閱讀和探索GraphQL文檔,以瞭解它提供的內容以及怎樣使你的API更好。