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.
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
- Node.js được cài đặt local mà bạn có thể thực hiện theo Cách cài đặt Node.js và Tạo Môi trường Phát triển Cục bộ .
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:
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 Express và express-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:
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 .
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
, name
và age
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 {...}
và 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:
// 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ã:
... // 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:
// 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 getUser
và retrieveUsers
mà bạn thêm vào trước đó để server.js
với những điều sau đây:
// 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
, age
và shark
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 age
và shark
.
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ả userA
và userB
. 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
- Faith
và Joy
.
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:
// 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 getUser
và retrieveUsers
, 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:
// 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:
// 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ó id
là 1
đã đượ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 .
Các tin liên quan