Thứ năm, 12/12/2019 | 00:00 GMT+7

Cách thiết lập server GraphQL trong Node.js với server Apollo và Sequelize


GraphQL là một đặc tả và do đó ngôn ngữ bất khả tri. Khi nói đến phát triển GraphQL với Node.js, có nhiều tùy chọn khác nhau có sẵn, từ graphql-js , express-graphql , đến apollo-server . Trong hướng dẫn này, bạn sẽ cài đặt một server GraphQL đầy đủ tính năng trong Node.js với Apollo Server.

Kể từ khi ra mắt Apollo Server 2, việc tạo một server GraphQL với Apollo Server đã trở nên hiệu quả hơn, chưa kể đến các tính năng khác đi kèm với nó.

Với mục đích của phần trình diễn này, ta sẽ xây dựng một server GraphQL cho một ứng dụng công thức nấu ăn.

Yêu cầu

Hướng dẫn này giả định những điều sau:

  • Node.js và NPM được cài đặt trên máy tính local của bạn
  • Kiến thức cơ bản về GraphQL

GraphQL là gì?

GraphQL là một đặc tả tìm nạp dữ liệu khai báo và ngôn ngữ truy vấn cho các API. Nó được tạo ra bởi Facebook. GraphQL là một giải pháp thay thế hiệu quả cho REST, vì nó được tạo ra để khắc phục một số thiếu sót của REST như tìm nạp dưới / quá mức.

Không giống như REST, GraphQL sử dụng một điểm cuối. Điều này nghĩa là ta thực hiện một yêu cầu tới điểm cuối và ta sẽ nhận được một phản hồi dưới dạng JSON. Phản hồi JSON này có thể chứa ít hoặc nhiều dữ liệu mà ta muốn. Giống như REST, GraphQL có thể được vận hành qua HTTP, mặc dù GraphQL là giao thức bất khả tri.

Một server GraphQL điển hình bao gồm schemas và trình phân giải. Một schemas (hoặc schemas GraphQL) chứa các định nghĩa kiểu sẽ tạo nên một API GraphQL. Định nghĩa kiểu chứa (các) trường, mỗi trường có những gì nó được mong đợi trả về. Mỗi trường được ánh xạ tới một hàm trên server GraphQL được gọi là trình phân giải. Bộ phân giải chứa logic triển khai và dữ liệu trả về cho một trường. Nói cách khác, các schemas chứa các định nghĩa kiểu, trong khi các trình phân giải chứa các triển khai thực tế.

Bước 1 - Cài đặt database

Ta sẽ bắt đầu bằng cách cài đặt database của bạn . Ta sẽ sử dụng SQLite cho database của bạn . Ngoài ra, ta sẽ sử dụng Sequelize , là một ORM cho Node.js, để tương tác với database của ta .

Đầu tiên, hãy tạo một dự án mới:

  • mkdir graphql-recipe-server
  • cd graphql-recipe-server
  • yarn init -y

Tiếp theo, hãy cài đặt Sequelize:

  • yarn add sequelize sequelize-cli sqlite3

Ngoài việc cài đặt Sequelize, ta cũng đang cài đặt gói sqlite3 cho Node.js. Để giúp ta xây dựng dự án của bạn , ta sẽ sử dụng Sequelize CLI mà ta cũng đang cài đặt.

Hãy xây dựng dự án của ta với CLI:

  • node_modules/.bin/sequelize init

Thao tác này sẽ tạo các folder sau:

config : chứa một file cấu hình, file này cho Sequelize biết cách kết nối với database của ta .
models : chứa tất cả các mô hình cho dự án của ta và cũng chứa index.js tích hợp tất cả các mô hình với nhau.
migrations : chứa tất cả các file di chuyển.
seeders : chứa tất cả các file hạt giống.

Với mục đích của hướng dẫn này, ta sẽ không tạo với bất kỳ trình seeding nào. Mở config/config.json và thay thế nó bằng nội dung sau:

// config/config.json  {   "development": {     "dialect": "sqlite",     "storage": "./database.sqlite"   } } 

Ta đặt dialect thành sqlite và đặt bộ storage trỏ đến file database SQLite.

Tiếp theo, ta cần tạo file database trực tiếp bên trong folder root của dự án:

  • touch database.sqlite

Bước 2 - Tạo mô hình và di chuyển

Với việc cài đặt database , ta có thể bắt đầu tạo các mô hình cho dự án của bạn . Ứng dụng công thức của ta sẽ có hai mô hình: UserRecipe . Ta sẽ sử dụng Sequelize CLI cho việc này:

  • node_modules/.bin/sequelize model:create --name User --attributes name:string,email:string,password:string

Thao tác này sẽ tạo file user.js bên trong folder models và file di chuyển tương ứng bên trong folder migrations .

Vì ta không muốn bất kỳ trường nào trên mô hình User là vô hiệu, ta cần xác định rõ điều đó. Mở migrations/XXXXXXXXXXXXXX-create-user.js và cập nhật định nghĩa các trường như sau:

// migrations/XXXXXXXXXXXXXX-create-user.js  name: {     allowNull: false,     type: Sequelize.STRING }, email: {     allowNull: false,     type: Sequelize.STRING }, password: {     allowNull: false,     type: Sequelize.STRING } 

Sau đó, ta sẽ làm tương tự trong mô hình User :

// models/user.js  name: {     type: DataTypes.STRING,     allowNull: false }, email: {     type: DataTypes.STRING,     allowNull: false }, password: {     type: DataTypes.STRING,     allowNull: false } 

Tiếp theo, hãy tạo mô hình Recipe :

  • node_modules/.bin/sequelize model:create --name Recipe --attributes title:string,ingredients:text,direction:text

Giống như ta đã làm với mô hình User , ta sẽ làm tương tự với mô hình Recipe . Mở migrations/XXXXXXXXXXXXXX-create-recipe.js và cập nhật định nghĩa các trường như sau:

// migrations/XXXXXXXXXXXXXX-create-recipe.js  userId: {     type: Sequelize.INTEGER.UNSIGNED,     allowNull: false }, title: {     allowNull: false,     type: Sequelize.STRING }, ingredients: {     allowNull: false,     type: Sequelize.STRING }, direction: {     allowNull: false,     type: Sequelize.STRING }, 

Bạn sẽ nhận thấy ta có một trường bổ sung: userId , trường này sẽ chứa ID của user đã tạo công thức. Thêm về điều này trong thời gian ngắn.

Cập nhật cả mô hình Recipe :

// models/recipe.js  title: {     type: DataTypes.STRING,     allowNull: false }, ingredients: {     type: DataTypes.STRING,     allowNull: false }, direction: {     type: DataTypes.STRING,     allowNull: false } 

Để kết thúc với các mô hình của ta , hãy xác định mối quan hệ giữa chúng. Bạn có thể đoán được với việc bao gồm cột userId vào bảng recipes , rằng ta muốn có thể liên kết công thức với user và ngược lại. Vì vậy, ta muốn có mối quan hệ một-nhiều giữa các mô hình của ta .

Mở models/user.js và cập nhật chức năng User.associate như sau:

// models/user.js  User.associate = function (models) {     User.hasMany(models.Recipe) } 

Ta cũng cần xác định nghịch đảo của mối quan hệ trên mô hình Recipe :

// models/recipe.js  Recipe.associate = function (models) {     Recipe.belongsTo(models.User, { foreignKey: 'userId' }) } 

Theo mặc định, Sequelize sẽ sử dụng tên hoa camel từ tên kiểu máy tương ứng và khóa chính của nó làm foreign keys . Vì vậy, trong trường hợp của ta , nó sẽ mong đợi foreign keys là UserId . Vì ta đặt tên cột khác nhau, ta cần xác định rõ ràng foreignKey trên liên kết.

Bây giờ, ta có thể chạy quá trình di chuyển:

  • node_modules/.bin/sequelize db:migrate

Bước 3 - Tạo Server GraphQL

Bây giờ, ta hãy đến phần GraphQL. Như đã đề cập trước đó, ta sẽ sử dụng Apollo Server để xây dựng server GraphQL của ta . Vì vậy, hãy cài đặt nó:

  • yarn add apollo-server graphql bcryptjs

Apollo Server yêu cầu graphql như một phụ thuộc, do đó cần phải cài đặt nó. Ngoài ra, ta cài đặt bcryptjs , mà ta sẽ sử dụng để băm password user sau này.

Với những thứ đã được cài đặt, hãy tạo một folder src , sau đó bên trong nó, tạo một index.js và thêm mã sau vào đó:

// src/index.js  const { ApolloServer } = require('apollo-server') const typeDefs = require('./schema') const resolvers = require('./resolvers') const models = require('../models')  const server = new ApolloServer({   typeDefs,   resolvers,   context: { models } })  server   .listen()   .then(({ url }) => console.log('Server is running on localhost:4000')) 

Ở đây, ta tạo một version mới của Apollo Server, chuyển tới nó schemas và các trình phân giải của ta (cả hai thứ mà ta sẽ tạo ngay sau đây). Ta cũng chuyển các mô hình làm bối cảnh cho Server Apollo. Điều này sẽ cho phép ta có quyền truy cập vào các mô hình từ trình phân giải của ta .

Cuối cùng, ta khởi động server .

Bước 4 - Xác định schemas GraphQL

Lược đồ GraphQL được sử dụng để xác định chức năng mà API GraphQL sẽ có. Một schemas GraphQL bao gồm các loại. Một loại có thể dùng để xác định cấu trúc của thực thể domain cụ thể của ta . Ngoài việc xác định các loại cho các thực thể cụ thể trong domain của ta , ta cũng có thể xác định các loại cho các hoạt động GraphQL, điều này sẽ chuyển sang chức năng mà API GraphQL sẽ có. Các thao tác này là: truy vấn, đột biến và đăng ký. Các truy vấn được sử dụng để thực hiện các thao tác đọc (tìm nạp dữ liệu) trên server GraphQL. Mặt khác, các đột biến được sử dụng để thực hiện các hoạt động ghi (chèn, cập nhật hoặc xóa dữ liệu) trên server GraphQL. Đăng ký hoàn toàn khác với hai đăng ký này, vì chúng được sử dụng để thêm chức năng thời gian thực vào server GraphQL.

Ta sẽ chỉ tập trung vào các truy vấn và đột biến trong hướng dẫn này.

Bây giờ ta đã hiểu schemas GraphQL là gì, hãy tạo schemas cho ứng dụng của ta . Trong folder src , tạo file schema.js và thêm mã sau vào đó:

// src/schema.js  const { gql } = require('apollo-server')  const typeDefs = gql`     type User {         id: Int!         name: String!         email: String!         recipes: [Recipe!]!       }      type Recipe {         id: Int!         title: String!         ingredients: String!         direction: String!         user: User!     }      type Query {         user(id: Int!): User         allRecipes: [Recipe!]!         recipe(id: Int!): Recipe     }      type Mutation {         createUser(name: String!, email: String!, password: String!): User!         createRecipe(           userId: Int!           title: String!           ingredients: String!           direction: String!         ): Recipe!     } `  module.exports = typeDefs 

Đầu tiên, ta lấy gói gql từ apollo-server . Sau đó, ta sử dụng nó để xác định schemas của ta . Lý tưởng nhất, ta muốn schemas GraphQL của ta phản ánh schemas database của ta càng nhiều càng tốt. Vì vậy, ta xác định hai loại, UserRecipe , tương ứng với các mô hình của ta . Trên Kiểu người User , ngoài việc xác định các trường ta có trên mô hình User , ta cũng xác định các trường recipes , sẽ được sử dụng để truy xuất công thức của user . Tương tự với loại Recipe ; ta xác định một trường user , trường này sẽ được sử dụng để lấy cho user một công thức.

Tiếp theo, ta xác định ba truy vấn: để tìm nạp một user , để tìm nạp tất cả các công thức đã được tạo và để tìm nạp một công thức tương ứng. Cả truy vấn userrecipe đều có thể trả về user hoặc công thức tương ứng hoặc trả về null nếu không tìm thấy kết quả phù hợp nào cho ID. Truy vấn allRecipes sẽ luôn trả về một mảng các công thức nấu ăn, có thể trống nếu chưa có công thức nào được tạo.

Cuối cùng, ta xác định các đột biến để tạo user mới cũng như tạo một công thức mới. Cả hai đột biến đều trả lại user và công thức đã tạo tương ứng.

Lưu ý: Các! biểu thị một trường là bắt buộc, trong khi [] biểu thị trường sẽ trả về một mảng các mục.

Bước 5 - Tạo trình phân giải

Trình phân giải xác định cách các trường trong schemas được thực thi. Nói cách khác, schemas của ta sẽ vô dụng nếu không có trình phân giải. Tạo file resolvers.js bên trong folder src và thêm mã sau vào đó:

// src/resolvers.js  const resolvers = {     Query: {         async user (root, { id }, { models }) {               return models.User.findById(id)         },         async allRecipes (root, args, { models }) {               return models.Recipe.findAll()         },         async recipe (root, { id }, { models }) {               return models.Recipe.findById(id)         }       }, }  module.exports = resolvers 

Ta bắt đầu bằng cách tạo các trình phân giải cho các truy vấn của bạn . Ở đây, ta đang sử dụng các mô hình để thực hiện các truy vấn cần thiết trên database và trả về kết quả.

Vẫn bên trong src/resolvers.js bcryptjs , hãy nhập bcryptjs ở đầu file :

// src/resolvers.js  const bcrypt = require('bcryptjs') 

Sau đó, thêm mã sau ngay sau đối tượng Query :

// src/resolvers.js  Mutation: {     async createUser (root, { name, email, password }, { models }) {         return models.User.create({             name,             email,             password: await bcrypt.hash(password, 10)           })     },     async createRecipe (root, { userId, title, ingredients, direction }, { models }) {         return models.Recipe.create({ userId, title, ingredients, direction })     } }, 

Đột biến createUser chấp nhận tên, email và password của một user và tạo một bản ghi mới trong database với các chi tiết được cung cấp. Ta đảm bảo băm password bằng cách sử dụng gói bcrypt trước khi lưu nó vào database . Nó trả về user mới được tạo. Đột biến createRecipe chấp nhận ID của user đang tạo công thức cũng như thông tin chi tiết cho chính công thức đó, lưu giữ họ vào database và trả về công thức mới được tạo.

Để kết thúc với các trình phân giải, hãy xác định cách ta muốn các trường tùy chỉnh của ta ( recipes trên Useruser trên Recipe ) được giải quyết. Thêm mã sau vào bên trong src/resolvers.js ngay sau đối tượng Mutation :

// src/resolvers.js  User: {     async recipes (user) {         return user.getRecipes()     } }, Recipe: {     async user (recipe) {         return recipe.getUser()     } } 

Chúng sử dụng các phương thức ( getRecipes() , getUser() ), được tạo sẵn trên các mô hình của ta bởi Sequelize do các mối quan hệ mà ta đã xác định.

Bước 6 - Kiểm tra Server GraphQL của ta

Đã đến lúc kiểm tra server GraphQL của ta . Đầu tiên, ta cần khởi động server bằng:

  • node src/index.js

Điều này sẽ chạy trên http: // localhost: 4000 và ta sẽ thấy GraphQL Playground đang chạy nếu ta truy cập nó. Hãy thử tạo một công thức mới:

# create a new recipe  mutation {   createRecipe(     userId: 3     title: "Sample 2"     ingredients: "Salt, Pepper"     direction: "Add salt, Add pepper"   ) {     id     title     ingredients     direction     user {       id       name       email     }   } } 

Ta sẽ thấy một kết quả như sau:

Sân chơi GraphQL sau khi tạo một công thức mới

Kết luận

Trong hướng dẫn này, ta đã xem xét cách tạo server GraphQL trong Node.js với Server Apollo. Ta cũng đã biết cách tích hợp database với server GraphQL bằng cách sử dụng Sequelize.


Tags:

Các tin liên quan

Cách xây dựng server GraphQL trong Node.js bằng GraphQL-yoga và MongoDB
2019-12-12
Cách sử dụng Ansible để tự động thiết lập server ban đầu trên Ubuntu 18.04
2019-11-27
Cách sử dụng Ansible để tự động thiết lập server ban đầu trên Ubuntu 18.04
2019-11-27
Cách sử dụng Ansible để tự động thiết lập server ban đầu trên Ubuntu 18.04
2019-11-27
Sử dụng Sự kiện do server gửi trong Node.js để tạo ứng dụng thời gian thực
2019-11-18
Cách thiết lập front-end server PageKite trên Debian 9
2019-10-25
Cách thiết lập server trang kết thúc phía trước PageKite trên Debian 9
2019-10-25
Cách cài đặt Linux, Apache, MariaDB, PHP (LAMP) trên Debian 10
2019-07-15
Thiết lập server ban đầu với Debian 10
2019-07-08
Cách cài đặt và cấu hình Postfix làm server SMTP chỉ gửi trên Debian 10
2019-07-08