如何創建安全的 Node.js GraphQL API

已發表: 2022-03-11

在本文中,我們旨在提供有關如何創建安全的 Node.js GraphQL API 的快速指南。

可能會想到的一些問題可能是:

  • 使用 GraphQL API 的目的是什麼?
  • 什麼是 GraphQL API?
  • 什麼是 GraphQL 查詢?
  • GraphQL 有什麼好處?
  • GraphQL 比 REST 更好嗎?
  • 為什麼我們使用 Node.js?

所有這些都是有效的問題,但在回答它們之前,我們應該深入了解一下 Web 開發的當前狀態:

  • 您今天會發現的幾乎所有解決方案都使用某種應用程序編程接口 (API)。
  • 即使您只是使用社交網絡,例如 Facebook 或 Instagram,您仍然連接到使用 API 的前端。
  • 如果你好奇,你會發現幾乎所有在線娛樂服務都使用不同類型的 API,包括 Netflix、Spotify 和 YouTube 等服務。

在幾乎所有場景中,您都會發現不需要詳細了解的 API,例如,您不需要知道它們是如何構建的,也不需要使用它們過去使用的相同技術能夠將其集成到您自己的系統中。 所提供的 API 允許您提供一種以通用標准在服務之間進行通信的方法,服務和客戶端都可以在無需依賴特定技術堆棧的情況下進行通信。

有了結構良好的 API,就有可能擁有一個可靠的、可維護的和可擴展的 API,它可以服務於多種客戶端和前端應用程序。

也就是說,什麼GraphQL API?

GraphQL 是一種 API 查詢語言,為 Facebook 內部使用而開發,並於 2015 年發布供公眾使用。它支持讀取、寫入和實時更新。 它也是開源的,通常與 REST 和其他架構進行比較。 簡而言之,它基於:

  • GraphQL 查詢- 這允許客戶端讀取和操作應如何接收數據。
  • GraphQL Mutations - 這是在服務器上寫入數據的方法。 它是關於如何將數據寫入系統的 GraphQL 約定。

儘管本文旨在展示一個關於如何構建和使用 GraphQL API 的簡單但真實的場景,但我們不會詳細介紹 GraphQL。 原因很簡單,因為 GraphQL 團隊提供了全面的文檔,並在他們的 GraphQL 簡介中列出了幾個最佳實踐。

什麼是 GraphQL 查詢?

如前所述,查詢是客戶端可以從 API 讀取和操作數據的方式。 您可以傳遞對象的類型並選擇要接收的字段類型。 一個簡單的查詢如下所示:

 query{ users{ firstName, lastName } }

在這個查詢中,我們試圖從用戶的模式中訪問所有用戶,但只接收firstNamelastName 。 此查詢的結果將類似於,例如:

 { "data": { "users": [ { "firstName": "Marcos", "lastName": "Silva" }, { "firstName": "Paulo", "lastName": "Silva" } ] } }

客戶端使用非常簡單。

使用 GraphQL API 的目的是什麼?

創建 API 的目的是使軟件能夠被其他外部服務集成為服務。 即使您的應用程序由單個前端使用,您也可以將此前端視為外部服務,為此,當通過 API 提供兩者之間的通信時,您將能夠在不同的項目中工作。

如果您在一個大型團隊中工作,則可以將其拆分為創建前端和後端團隊,從而使兩者可以使用相同的技術並使工作更輕鬆。 在構建 API 時,重要的是選擇更適合項目的方式以及使您更接近所需解決方案的方式。

在本文中,我們將專注於構建使用 GraphQL 的 API 的框架。

GraphQL 比 REST 更好嗎?

這可能有點逃避現實,但我情不自禁:這取決於.

GraphQL 是一種非常適合多種場景的方法。 REST 是一種架構方法,在多個場景中也得到了證明。 如今,有大量文章解釋了為什麼一個比另一個更好,或者為什麼你應該只使用 REST 而不是 GraphQL。 此外,您可以通過多種方式在內部使用 GraphQL,並且仍然將 API 的端點維護為基於 REST 的架構。

最好的指導是了解每種方法的好處,分析您正在創建的解決方案,評估您的團隊使用該解決方案的舒適程度,並評估您是否能夠指導您的團隊學習和掌握在選擇方法之前快速加快速度。

本文更多的是實用指南,而不是 GraphQL 和 REST 的主觀比較。 如果您想閱讀兩者的詳細比較,我建議您查看我們的另一篇文章,GraphQL vs. REST - A GraphQL Tutorial。

在今天的文章中,我們將重點介紹使用 Node.js 創建 GraphQL API。

為什麼我們使用 Node.js?

GraphQL 有幾個不同的庫可供您使用。 出於本文的目的,我們決定將 JavaScript 與 Node.js 一起使用,因為 Node.js 的廣泛使用以及 Node.js 允許開發人員使用熟悉的前端語法進行服務器端開發這一事實。

將我們的方法與基於 REST 的 API 進行比較也很有用,類似於在另一篇 Toptal 工程博客文章中演示的方法:在 Node.js 中創建安全的 REST API。 本文還展示了使用 Node.js 和 Express 來開發一個骨架 REST API,這將允許您比較這兩種方法之間的一些差異。 Node.js 還設計有可擴展的網絡應用程序、一個全球社區和幾個您可以在 npm 網站上找到的開源庫。

這一次,我們將展示如何使用 GraphQL、Node.js 和 Express 構建框架 API!

上手 GraphQL 教程

如前所述,我們將為 GraphQL API 構建一個框架概念,在繼續之前您需要了解 Node.js 和 Express 的基礎知識。 為這個 GraphQL 示例製作的項目的源代碼可在此處獲得。

我們將處理兩種類型的資源:

  • 用戶,我們將為其處理基本的 CRUD。
  • 產品,我們將有一些細節來展示更多 GraphQL 的力量。

用戶將包含以下結構:

  • ID
  • 電子郵件
  • 密碼
  • 權限級別

產品將包含以下結構:

  • ID
  • 姓名
  • 描述
  • 價錢

至於編碼標準,我們將在這個項目中使用 TypeScript。 在源文件中,您將能夠配置所有內容以開始使用 TypeScript 進行編碼。

讓我們編碼吧!

首先,確保您安裝了最新的 Node.js 版本。 根據 Nodejs.org,在發佈時,當前版本是 10.15.3。

初始化項目

讓我們從一個可以命名為node-graphql的新文件夾開始。 在那裡,我們可以打開終端或 Git CLI 控制台並使用以下命令啟動魔法: npm init

配置我們的依賴和 TypeScript

為了加快這個過程,在我們的 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 。 它將安裝在 Node.js 和 Express 中運行此 GraphQL API 所需的所有依賴項。

下一部分是配置我們的 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 教程”。 在後面的場景中,配置基本上是將 TypeScript 代碼編譯成純 JavaScript,然後在build文件夾中執行我們的構建。

現在讓我們為我們的 GraphQL API 配置一個基本框架。 為了開始我們的項目,我們將添加三個基本導入:

  • 表示
  • Express-graphql
  • Graphql 工具

讓我們開始把它們放在一起:

 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}!`));

我們正在做的是:

  • 為我們的 Express 服務器應用啟用端口 3000。
  • 定義我們想要使用的查詢和突變作為一個快速示例。
  • 定義查詢和突變如何工作。

好的,但是 typeDefs 和解析器發生了什麼,以及與查詢和突變的關係?

  • typeDefs - 我們可以從查詢和突變中獲得的模式的定義。
  • 解析器- 在這裡我們定義查詢和突變應該如何工作的功能和行為,而不是字段或必需參數的期望。
  • 查詢- 我們要從服務器讀取的“獲取”。
  • 突變- 我們的請求將影響我們自己服務器上的任何數據。

現在,讓我們再次運行npm start看看我們有什麼。 我們希望應用程序運行時會顯示以下消息:Node Graphql API 在端口 3000 上偵聽!

我們現在可以嘗試通過以下方式在我們自己的服務器上查詢和測試 GraphQL API:http://localhost:3000/graphql

GraphQL 教程:服務器測試

太好了,現在我們可以編寫我們自己的第一個查詢,它被定義為“hello”。

GraphQL 教程:第一個查詢

請注意,我們在typeDefs中定義它的方式,頁面可以幫助我們構建查詢。

這很好,但我們如何改變價值呢? 突變!

現在,讓我們看看當我們使用突變更改內存中的值時會發生什麼:

GraphQL 教程:突變演示

現在我們可以使用我們的 GraphQL Node.js API 進行基本的 CRUD 操作。 現在讓我們推進我們的代碼。

產品

對於產品,我們將使用一個名為 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; }; } }

用戶

對於用戶,我們將遵循與產品模塊相同的結構。 我們將為用戶提供模型和服務。 該模型將定義為:

 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; }; } }

提醒一下,可以從此鏈接使用源代碼。

現在我們可以播放和測試我們的代碼了。 讓我們運行npm start 。 我們將在端口 3000 上運行服務器。我們現在可以在 http://localhost:3000/graphql 訪問 GraphQL 進行測試。

讓我們嘗試一個突變來將一個項目添加到我們的產品列表中:

Node.js GraphQL 變異演示

為了測試它是否有效,我們現在將使用產品查詢,但只接收idnameprice

 query{ products{ id, name, price } } The response will be: { "data": { "products": [ { "id": 100, "name": "My amazing product", "price": 400 } ] } }

就是這樣; 產品按預期工作。 現在我們可以根據需要播放和切換字段。 您可以嘗試添加描述:

 query{ products{ id, name, description, price } }

現在我們可以得到我們產品的描述。 現在讓我們試試用戶。

 mutation{ user(id:200, firstName:"Marcos", lastName:"Silva", password:"amaz1ingP4ss", permissionLevel:9, email:"[email protected]") { id } }

查詢將類似於:

 query{ users{ id, firstName, lastName, password, email } }

回復如下:

 { "data": { "users": [ { "id": 200, "firstName": "Marcos", "lastName": "Silva", "password": "kpj6Mq0tGChGbZ+BT9Nw6RMCLReZEPPyBCaUS3X23lZwCCp1Ogb94/oqJlya0xOBdgEbUwqRSuZRjZGhCzLdeQ==", "email": "[email protected]" } ] } }

現在我們的 GraphQL 骨架已經準備好了! 從這裡到一個有用的、功能齊全的 API 有很多步驟,但現在已經設置了基本的核心。

總結和最終想法

即使是縮短的前沿,這篇文章也相當大,包含很多關於開發 GraphQL Node.js API 的基本信息。

讓我們回顧一下到目前為止我們所涵蓋的內容:

  • 使用 Node.js 與 Express 和 GraphQL 構建 GraphQL API;
  • 基本的 GraphQL 使用;
  • 查詢和突變的基本用法;
  • 為您的項目創建模塊的基本方法;
  • 測試我們的 GraphQL API;

為了更專注於事物的開發方面,我們避免了幾個重要的項目,可以簡要總結如下:

  • 新項目的驗證;
  • 使用通用錯誤服務正確處理錯誤;
  • 通過通用服務驗證用戶可以在每個請求中使用的字段;
  • 添加 JWT 攔截器以保護 API;
  • 用更有效的方法處理密碼哈希;
  • 添加單元和集成測試;

請記住,我們在這個 Git 鏈接上有完整的源代碼。 隨意使用、分叉、打開問題、提出拉取請求並使用它! 請注意,本文中提出的所有標準和建議都不是一成不變的。

這只是可用於開始設計您自己的 GraphQL API 的眾多方法之一。 此外,請務必更詳細地閱讀和探索 GraphQL,了解它提供的功能以及它如何使您的 API 變得更好。