Thứ tư, 29/04/2020 | 00:00 GMT+7

Hướng dẫn bắt đầu GraphQL thực tế với Node.js và Express

GraphQL là một ngôn ngữ truy vấn do Facebook tạo ra với mục đích xây dựng các ứng dụng client dựa trên cú pháp trực quan và linh hoạt để mô tả các yêu cầu và tương tác dữ liệu của chúng. Dịch vụ GraphQL được tạo bằng cách xác định các kiểu và trường trên các kiểu đó, sau đó cung cấp các hàm cho từng trường trên mỗi kiểu.

Khi dịch vụ GraphQL đang chạy (thường là tại URL trên dịch vụ web), nó có thể nhận các truy vấn GraphQL để xác thực và thực thi. Trước tiên, một truy vấn nhận được sẽ được kiểm tra đảm bảo nó chỉ tham chiếu đến các kiểu và trường được xác định, sau đó chạy các hàm được cung cấp để tạo ra kết quả.

Trong hướng dẫn này, ta sẽ triển khai một server GraphQL bằng Express và sử dụng nó để tìm hiểu các tính năng quan trọng của GraphQL.

GraphQL

Một số tính năng của GraphQL bao gồm:

  • Phân cấp - Các truy vấn trông giống hệt như dữ liệu mà chúng trả về.

  • Truy vấn do client chỉ định - Máy khách có quyền tự do ra lệnh tìm nạp những gì từ server .

  • Được gõ mạnh - Bạn có thể xác thực một truy vấn theo cú pháp và trong hệ thống kiểu GraphQL trước khi thực thi. Điều này cũng giúp tận dụng các công cụ mạnh mẽ giúp cải thiện trải nghiệm phát triển, chẳng hạn như GraphiQL.

  • Introspective - Bạn có thể truy vấn hệ thống kiểu bằng chính cú pháp GraphQL. Điều này rất tốt để phân tích cú pháp dữ liệu đến thành các giao diện được đánh máy mạnh và không phải xử lý việc phân tích cú pháp và chuyển đổi thủ công JSON thành các đối tượng.

Bàn thắng

Một trong những thách thức chính với các cuộc gọi REST truyền thống là khách hàng không có khả năng yêu cầu một bộ dữ liệu tùy chỉnh (giới hạn hoặc mở rộng). Trong hầu hết các trường hợp, một khi client yêu cầu thông tin từ server , nó sẽ nhận được tất cả hoặc không có trường nào.

Một khó khăn khác là làm việc và duy trì nhiều điểm cuối. Khi một nền tảng phát triển, số lượng sẽ tăng lên. Do đó, khách hàng thường cần yêu cầu dữ liệu từ các điểm cuối khác nhau. Các API GraphQL được tổ chức theo kiểu và trường, không phải điểm cuối. Bạn có thể truy cập toàn bộ khả năng dữ liệu của bạn từ một điểm cuối duy nhất.

Khi xây dựng server GraphQL, chỉ cần có một URL cho tất cả việc tìm nạp và thay đổi dữ liệu. Do đó, một client có thể yêu cầu một tập hợp dữ liệu bằng cách gửi một chuỗi truy vấn, mô tả những gì họ muốn, đến một server .

Yêu cầu

Bước 1 - Cài đặt GraphQL với Node

Bạn sẽ bắt đầu bằng cách tạo cấu trúc file cơ bản và đoạn mã mẫu.

Đầu tiên tạo một folder GraphQL :

  • mkdir GraphQL

Thay đổi vào folder mới:

  • cd GraphQL

Khởi tạo một dự án npm :

  • npm init -y

Sau đó, tạo file server.js sẽ là file chính:

  • touch server.js

Dự án của bạn sẽ giống như sau:

Liệt kê nội dung folder .

Các gói cần thiết sẽ được thảo luận trong hướng dẫn này khi chúng được triển khai. Tiếp theo, cài đặt một server bằng Expressexpress-graphql , một phần mềm trung gian của server HTTP:

  • npm i graphql express express-graphql -S

Mở server.js trong editor và thêm các dòng mã sau:

server.js
var express = require('express'); var graphqlHTTP = require('express-graphql'); var { buildSchema } = require('graphql');  // Initialize a GraphQL schema var schema = buildSchema(`   type Query {     hello: String   } `);  // Root resolver var root = {    hello: () => 'Hello world!' };  // Create an express server and a GraphQL endpoint var app = express(); app.use('/graphql', graphqlHTTP({   schema: schema,  // Must be provided   rootValue: root,   graphiql: true,  // Enable GraphiQL when server endpoint is accessed in browser })); app.listen(4000, () => console.log('Now browse to localhost:4000/graphql')); 

Đoạn mã này hoàn thành một số điều. Nó sử dụng require bao gồm các gói đã được cài đặt. Nó cũng khởi tạo schema chung và giá trị root . Hơn nữa, nó tạo ra một điểm cuối tại /graphql có thể được truy cập bằng trình duyệt web.

Lưu file sau khi thực hiện những thay đổi này.

Khởi động server Node nếu nó không chạy:

  • node server.js

Lưu ý: Trong suốt hướng dẫn này, bạn sẽ thực hiện các bản cập nhật cho server.js sẽ yêu cầu khởi động lại server nút để áp dụng thay đổi mới nhất.

Truy cập localhost:4000/graphql trong trình duyệt web. Bạn sẽ thấy giao diện web Chào mừng đến với GraphiQL .

Sẽ có một ngăn bên trái nơi bạn sẽ nhập các truy vấn. Có một ngăn bổ sung để nhập các biến truy vấn mà bạn có thể cần kéo và thay đổi kích thước để xem. Khung bên phải sẽ hiển thị kết quả thực hiện các truy vấn của bạn. Hơn nữa, việc thực thi các truy vấn có thể được thực hiện bằng cách nhấn vào nút có biểu tượng phát .

Ảnh chụp màn hình giao diện web GraphiQL

Cho đến nay ta đã khám phá một số tính năng và lợi thế của GraphQL. Trong phần tiếp theo này, ta sẽ đi sâu vào các thuật ngữ và cách triển khai khác nhau của một số tính năng kỹ thuật trong GraphQL. Ta sẽ sử dụng server Express để thực hành các tính năng này.

Bước 2 - Xác định một schemas

Trong GraphQL, Schema quản lý các truy vấn và đột biến, xác định những gì được phép thực thi trong server GraphQL. Một schemas xác định loại hệ thống API của GraphQL. Nó mô tả tập hợp đầy đủ các dữ liệu có thể có (đối tượng, trường, mối quan hệ, v.v.) mà client có thể truy cập. Các cuộc gọi từ client được xác thực và thực hiện dựa trên schemas . Khách hàng có thể tìm thông tin về schemas thông qua xem xét nội quan . Một schemas nằm trên server API GraphQL.

Ngôn ngữ định nghĩa giao diện GraphQL (IDL) hoặc Ngôn ngữ định nghĩa schemas (SDL) là những cách ngắn gọn nhất để chỉ định một schemas GraphQL. Các thành phần cơ bản nhất của schemas GraphQL là các kiểu đối tượng, đại diện cho một loại đối tượng mà ta có thể lấy từ dịch vụ của bạn và nó có những trường nào.

Trong ngôn ngữ schemas GraphQL, bạn có thể đại diện cho một user với id , nameage như ví dụ sau:

type User {   id: ID!   name: String!   age: Int } 

Trong JavaScript, bạn sẽ sử dụng hàm buildSchema để xây dựng một đối tượng Schema từ ngôn ngữ schemas GraphQL. Nếu bạn đại diện cho cùng một user ở trên, nó sẽ giống như ví dụ sau:

var schema = buildSchema(`   type User {     id: Int     name: String!     age: Int   } `); 

Các loại cấu tạo

Bạn có thể xác định các kiểu khác nhau bên trong buildSchema , mà bạn có thể nhận thấy trong hầu hết các trường hợp là type Query {...}type Mutation {...} . type Query {...} là một đối tượng chứa các hàm sẽ được ánh xạ tới các truy vấn GraphQL, được sử dụng để tìm nạp dữ liệu (tương đương với GET trong REST). type Mutation {...} chứa các hàm sẽ được ánh xạ tới các đột biến, được sử dụng để tạo, cập nhật hoặc xóa dữ liệu (tương đương với POST, UPDATE và DELETE trong REST).

Bạn sẽ làm cho giản đồ của bạn hơi phức tạp bằng cách thêm một số kiểu hợp lý. Ví dụ: bạn muốn trả về một user và một mảng users kiểu Person , những người có id , name , age và các thuộc tính shark yêu thích của họ.

Thay thế các dòng mã đã có trước cho schema trong server.js bằng đối tượng Giản đồ mới này:

server.js
// Initialize a GraphQL schema var schema = buildSchema(`   type Query {     user(id: Int!): Person     users(shark: String): [Person]   },   type Person {     id: Int     name: String     age: Int     shark: String   } `); 

Bạn có thể nhận thấy một số cú pháp thú vị ở trên, [Person] nghĩa là trả về một mảng kiểu Person trong khi dấu chấm than trong user(id: Int!) nghĩa là id phải được cung cấp. truy vấn users nhận một biến shark tùy chọn.

Bước 3 - Xác định bộ phân giải

Một trình phân giải chịu trách nhiệm ánh xạ hoạt động tới một chức năng thực tế. Bên trong type Query , bạn có một hoạt động được gọi là users . Bạn ánh xạ thao tác này với một hàm có cùng tên bên trong root .

Bạn cũng cần tạo một số user mẫu cho chức năng này.

Thêm các dòng mới của mã để server.js ngay sau khi buildSchema dòng mã, nhưng trước khi root dòng mã:

server.js
... // Sample users var users = [   {     id: 1,     name: 'Brian',     age: '21',     shark: 'Great White Shark'   },   {     id: 2,     name: 'Kim',     age: '22',     shark: 'Whale Shark'   },   {     id: 3,     name: 'Faith',     age: '23',     shark: 'Hammerhead Shark'   },   {     id: 4,     name: 'Joseph',     age: '23',     shark: 'Tiger Shark'   },   {     id: 5,     name: 'Joy',     age: '25',     shark: 'Hammerhead Shark'   } ];  // Return a single user var getUser = function(args) {   // ... }  // Return a list of users var retrieveUsers = function(args) {    // ... } ... 

Thay thế các dòng mã có sẵn từ trước cho root trong server.js bằng đối tượng mới này:

server.js
// Root resolver var root = {    user: getUser,  // Resolver function to return user with specific id   users: retrieveUsers }; 

Để làm cho mã dễ đọc hơn, hãy tạo các hàm riêng biệt thay vì chất đống mọi thứ trong trình giải quyết root . Cả hai hàm đều nhận một tham số args tùy chọn mang các biến từ truy vấn client . Hãy cung cấp triển khai cho các trình phân giải và kiểm tra chức năng của chúng.

Thay thế các dòng mã cho getUserretrieveUsers mà bạn thêm vào trước đó để server.js với những điều sau đây:

server.js
// Return a single user (based on id) var getUser = function(args) {   var userID = args.id;   return users.filter(user => user.id == userID)[0]; }  // Return a list of users (takes an optional shark parameter) var retrieveUsers = function(args) {   if (args.shark) {     var shark = args.shark;     return users.filter(user => user.shark === shark);   } else {     return users;   } } 

Trong giao diện web, nhập truy vấn sau vào ngăn nhập liệu:

query getSingleUser {   user {     name     age     shark   } } 

Bạn sẽ nhận được kết quả sau:

Output
{ "errors": [ { "message": "Cannot query field \"user\" on type \"Query\".", "locations": [ { "line": 2, "column": 3 } ] } ] }

Trong ví dụ trên, ta đang sử dụng một hoạt động có tên getSingleUser để lấy một user duy nhất có name , ageshark yêu thích của họ. Ta có thể tùy ý chỉ định rằng ta chỉ cần name của họ nếu ta không cần ageshark .

Theo tài liệu chính thức , việc xác định các truy vấn trong cơ sở mã của bạn bằng tên thay vì giải mã nội dung là điều dễ dàng nhất.

Truy vấn này không cung cấp id bắt buộc và GraphQL cung cấp cho ta thông báo lỗi mô tả. Bây giờ ta sẽ thực hiện một truy vấn chính xác. Chú ý đến việc sử dụng các biến và đối số.

Trong giao diện web, thay thế nội dung của ngăn nhập bằng truy vấn đã sửa sau:

query getSingleUser($userID: Int!) {   user(id: $userID) {     name     age     shark   } } 

Khi vẫn ở trong giao diện web, hãy thay thế nội dung của ngăn biến bằng nội dung sau:

Query Variables
{ "userID": 1 }

Bạn sẽ nhận được kết quả sau:

Output
{ "data": { "user": { "name": "Brian", "age": 21, "shark": "Great White Shark" } } }

Điều này trả về một user phù hợp với id của 1 , Brian . Nó cũng trả về name , age và các trường shark được yêu cầu.

Bước 4 - Xác định alias

Trong tình huống bạn cần truy xuất hai user khác nhau, bạn có thể tự hỏi làm cách nào để xác định từng user . Trong GraphQL, bạn không thể truy vấn trực tiếp cho cùng một trường với các đối số khác nhau. Hãy chứng minh điều này.

Trong giao diện web, thay thế nội dung của ngăn nhập bằng nội dung sau:

query getUsersWithAliasesError($userAID: Int!, $userBID: Int!) {   user(id: $userAID) {     name     age     shark   },   user(id: $userBID) {     name     age     shark   } } 

Khi vẫn ở trong giao diện web, hãy thay thế nội dung của ngăn biến bằng nội dung sau:

Query Variables
{ "userAID": 1, "userBID": 2 }

Bạn sẽ nhận được kết quả sau:

Output
{ "errors": [ { "message": "Fields \"user\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 2, "column": 3 }, { "line": 7, "column": 3 } ] } ] }

Lỗi này mang tính mô tả và thậm chí gợi ý việc sử dụng alias . Hãy chỉnh sửa việc thực hiện.

Trong giao diện web, thay thế nội dung của ngăn nhập bằng truy vấn đã sửa sau:

query getUsersWithAliases($userAID: Int!, $userBID: Int!) {   userA: user(id: $userAID) {     name     age     shark   },   userB: user(id: $userBID) {     name     age     shark   } } 

Trong khi vẫn ở trong giao diện web, hãy đảm bảo ngăn biến chứa những điều sau:

Query Variables
{ "userAID": 1, "userBID": 2 }

Bạn sẽ nhận được kết quả sau:

Output
{ "data": { "userA": { "name": "Brian", "age": 21, "shark": "Great White Shark" }, "userB": { "name": "Kim", "age": 22, "shark": "Whale Shark" } } }

Bây giờ ta có thể xác định chính xác từng user với các trường của họ.

Bước 5 - Tạo phân mảnh

Truy vấn ở trên không tệ lắm, nhưng nó có một vấn đề; ta đang lặp lại các trường giống nhau cho cả userAuserB . Ta có thể tìm thấy thứ gì đó sẽ làm cho các truy vấn của ta trở nên KHÔ . GraphQL bao gồm các đơn vị có thể tái sử dụng được gọi là phân đoạn cho phép bạn tạo các tập hợp trường, sau đó đưa chúng vào các truy vấn khi bạn cần.

Trong giao diện web, thay thế nội dung của ngăn biến bằng nội dung sau:

query getUsersWithFragments($userAID: Int!, $userBID: Int!) {   userA: user(id: $userAID) {     ...userFields   },   userB: user(id: $userBID) {     ...userFields   } }  fragment userFields on Person {   name   age   shark } 

Trong khi vẫn ở trong giao diện web, hãy đảm bảo ngăn biến chứa những điều sau:

Query Variables
{ "userAID": 1, "userBID": 2 }

Bạn sẽ nhận được kết quả sau:

Output
{ "data": { "userA": { "name": "Brian", "age": 21, "shark": "Great White Shark" }, "userB": { "name": "Kim", "age": 22, "shark": "Whale Shark" } } }

Bạn đã tạo một phân đoạn được gọi là userFields chỉ có thể được áp dụng trên type Person và sau đó sử dụng nó để truy xuất user .

Bước 6 - Xác định Chỉ thị

Các hướng cho phép ta thay đổi cấu trúc và hình dạng của các truy vấn bằng cách sử dụng các biến. Tại một số thời điểm, bạn có thể cần bỏ qua hoặc bao gồm một số trường mà không cần thay đổi schemas . Hai chỉ thị có sẵn như sau:

  • @include(if: Boolean) - Chỉ đưa trường này vào kết quả nếu đối số là đúng.
  • @skip(if: Boolean) - Bỏ qua trường này nếu đối số là đúng.

Giả sử bạn muốn truy xuất những user là người hâm mộ của Hammerhead Shark , nhưng bao gồm id của họ và bỏ qua các trường age của họ. Bạn có thể sử dụng các biến để truyền vào shark và sử dụng các chỉ thị cho các chức năng bao gồm và bỏ qua.

Trong giao diện web, xóa ngăn nhập liệu và thêm những thứ sau:

query getUsers($shark: String, $age: Boolean!, $id: Boolean!) {   users(shark: $shark){     ...userFields   } }  fragment userFields on Person {   name   age @skip(if: $age)   id @include(if: $id) } 

Trong khi vẫn ở trong giao diện web, hãy xóa ngăn biến và thêm những thứ sau:

Query Variables
{ "shark": "Hammerhead Shark", "age": true, "id": true }

Bạn sẽ nhận được kết quả sau:

Output
{ "data": { "users": [ { "name": "Faith", "id": 3 }, { "name": "Joy", "id": 5 } ] } }

Điều này trả về hai user có giá trị shark phù hợp với Hammerhead Shark - FaithJoy .

Bước 7 - Xác định đột biến

Lúc này, ta đã giải quyết các truy vấn, các hoạt động để lấy dữ liệu. Các đột biến là hoạt động chính thứ hai trong GraphQL liên quan đến việc tạo, xóa và cập nhật dữ liệu.

Hãy tập trung vào một số ví dụ về cách thực hiện đột biến. Ví dụ: ta muốn cập nhật user có id == 1 và thay đổi age , name , sau đó trả lại chi tiết user mới.

Cập nhật giản đồ của bạn để bao gồm một loại đột biến ngoài các dòng mã đã có trước:

server.js
// Initialize a GraphQL schema var schema = buildSchema(`   type Query {     user(id: Int!): Person     users(shark: String): [Person]   },   type Person {     id: Int     name: String     age: Int     shark: String   }   # newly added code   type Mutation {     updateUser(id: Int!, name: String!, age: String): Person   } `); 

Sau getUserretrieveUsers , thêm một mới updateUser chức năng để xử lý việc cập nhật một người sử dụng:

server.js
// Update a user and return new user details var updateUser = function({id, name, age}) {   users.map(user => {     if (user.id === id) {       user.name = name;       user.age = age;       return user;     }   });   return users.filter(user => user.id === id)[0]; } 

Cũng cập nhật trình giải quyết root với các chức năng của trình giải quyết có liên quan:

server.js
// Root resolver var root = {    user: getUser,   users: retrieveUsers,   updateUser: updateUser  // Include mutation function in root resolver }; 

Giả sử đây là những chi tiết user ban đầu:

Output
{ "data": { "user": { "name": "Brian", "age": 21, "shark": "Great White Shark" } } }

Trong giao diện web, thêm truy vấn sau vào ngăn nhập liệu:

mutation updateUser($id: Int!, $name: String!, $age: String) {   updateUser(id: $id, name:$name, age: $age){     ...userFields   } }  fragment userFields on Person {   name   age   shark } 

Trong khi vẫn ở trong giao diện web, hãy xóa ngăn biến và thêm những thứ sau:

Query Variables
{ "id": 1, "name": "Keavin", "age": "27" }

Bạn sẽ nhận được kết quả sau:

Output
{ "data": { "updateUser": { "name": "Keavin", "age": 27, "shark": "Great White Shark" } } }

Sau khi đột biến để cập nhật user , bạn sẽ nhận được thông tin chi tiết về user mới.

User có id1 đã được cập nhật từ Brian ( age 21 ) thành Keavin ( age 27 ).

Kết luận

Trong hướng dẫn này, bạn đã trình bày các khái niệm cơ bản về GraphQL đến một số ví dụ khá phức tạp. Hầu hết các ví dụ này tiết lộ sự khác biệt giữa GraphQL và REST cho những user đã tương tác với REST.

Để tìm hiểu thêm về GraphQL, hãy kiểm tra tài liệu chính thức .


Tags:

Các tin liên quan