GraphQL là gì
GraphQL là một ngôn ngữ thao tác và truy vấn dữ liệu mã nguồn mở cho các API và một thời gian chạy để thực hiện các truy vấn với dữ liệu hiện có. GraphQL được Facebook phát triển nội bộ vào năm 2012 trước khi phát hành công khai vào năm 2015.
Ngắn gọn thì graphQL đang là ngôn ngữ truy vấn mà ở đó ng ta muốn nó có khả năng thay thế RestApi trong tương lai.
Lý do là rest Api đang bộc lộ vài hạn chế đang có về việc trả ra resource cố định không mang tính dynamic gây dư thừa.
Setting up GraphQL and Express
Mình dùng express khá quen thuộc nên khi sử dụng graphQL cugnx thấy nó khá nuột. Bạn code node chắc không xa lạ gì với express 😄 vậy để cài nhanh express và graphQL thì dùng như sau: app nodejs đầu tiên sử dụng Express application generator
# thêm cơ sở dữ liệu mình chọn là mongo
npm install mongoose
# cài đặt graphql và express-graphql
npm install graphql apollo-server-express
# viết api mà để kết nối không bị chặn phải dùng cors
npm i cors
Và cuối cùng chúng ta có file của package.json như sau:
{
"name": "learn",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www",
"watch": "nodemon ./bin/www",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"apollo-server-express": "^2.19.2",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"graphql": "^15.5.0",
"http-errors": "~1.6.3",
"mongoose": "^5.11.15",
"morgan": "~1.9.1",
"nodemon": "^2.0.15"
}
}
Code thôi ! :D
Tạo server graphQL đơn giản nhất có thể
GraphQL được cấu thành bởi
- middleware đính kèm trong app express ( lát sẽ sửa file app.js )
- chema là cấu trúc bạn muốn mô tả các thực thể cho graphQL
- resolvers là định nghĩa những function mô tả cách trả về dữ liệu tương ứng cho client
1. tạo schema
tạo file : /schema/schema.js với nội dung:
const { gql } = require('apollo-server-express')
const schema = gql`
type Book {
id: ID
name: String
}
# ROOT TYPE
type Query {
books: [Book]
book(id: ID!): Book
}
`
module.exports = schema
Chỗ type Book
là định nghĩa bạn sẽ có 1 kiểu dữ liệu trả về là Book có 2 thuộc tính là (id, name).
Chỗ ROOT TYPE
đang định nghĩa type Query
giống như 1 select trong sql vậy. Nó sẽ có khả năng để client query data và tạm hiểu là 1 Query data. Trong Query thì có khai báo 2 function: books kiểu trả về là danh sách Book ( được định nghĩa ở trên) và 1 function trả về book theo id
2 . tạo fileresolvers
Tạo file /resolver/resolver.js với nội dung:
const resolvers = {
// QUERY
Query: {
books: async () => Promise.resolve(
[
{ id: 3123213, name: "sách học tán gái" },
{ id: 343, name: "lập trình chứ không hề cô đơn" },
]
),
book: async () => Promise.resolve({ id: 343, name: "lập trình chứ không hề cô đơn" })
}
}
module.exports = resolvers
Giải thích: Trong đây chúng ta đang hardcode trả về dữ liệu cho 2 function books và book id trong schema.js định nghĩa trước đó. nghĩa là file resolvers.js là file mô tả cụ thể hành vi. File schema mô tả cấu trúc dữ liệu.
3. Chỉnh file app gắn middleware
vào file app.js thêm đoạn code sau vào bên dưới dòng var app = express();
const { ApolloServer } = require('apollo-server-express')
// Load schema & resolvers
const typeDefs = require('./schema/schema')
const resolvers = require('./resolver/resolver')
const server = new ApolloServer({
typeDefs,
resolvers,
})
server.applyMiddleware({ app })
console.log(`path graphQL la: ${server.graphqlPath}`)
ảnh minh hoạ:
Sau khi bạn thêm xong là thành công rồi. Chỉ còn thực thi code nodejs và test truy vấn với prlayGround cảu graphQL nữa thôi
Run code node và query thử
# chạy node với lệnh
node ./bin/www
Nếu không vấn đề thì bạn có thể chạy http://localhost:3000/graphql để chạy playground:
bạn gõ vào trong đó query thử như sau:
query hungDepTrai{
books {
id,
name
}
}
Kết quả:
Sau thực thi xong bạn sẽ thấy kết quả là các hardcode của bạn. Và câu chuyện bạn cần có kết nối vào db để lấy ra data thật. Và mình sẽ kết nối mongodb bằng mongoose. Nếu bạn nào chưa làm việc với mongoose thì xem qua bài viết này của mình: Kết nối đến MongoDB với Nodejs dùng mongoose và dùng schema để làm việc với mongoose trong nodejs mongodb
Mình tạm hiểu là bạn flow theo 2 bài viết của mình trên kia và biết cách connect đến mongodb rồi thì tiếp theo chúng ta tạo 1 schema cho hệ thống là:
// File models/book.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const BookSchema = new Schema({
name: {
type: String
},
description: {
type: String
},
authorId: {
type: String
}
})
module.exports = mongoose.model('books', BookSchema)
và file Author:
// File models/author.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const AuthorSchema = new Schema({
name: {
type: String
},
age: {
type: Number
}
})
module.exports = mongoose.model('authors', AuthorSchema)
Sửa lại resolvers là 1 function lấy data thực từ db ra như sau:
const Book = require('../models/Book')
const resolvers = {
// QUERY
Query: {
books: async (parent, args, context) => await Book.find({}),
book: async (parent, args, context) => {
const { id } = args // id truyền vào từ câu query theo định nghĩa của file /schema/schema.js: book(id: ID!): Book
return await Book.findById(id)
},
}
}
module.exports = resolvers
Vào query thử thì thấy data có gì đâu 😄 Kết quả nè :
Ừ thì mình đã làm CUD đâu nè. Muốn có data phải qua phần 2 để làm việc với mutation nha.
II. Phần 2 -Mutation và tích hợp thêm mongoose
Với mỗi ứng dụng thì điều cơ bản phải có là CRUD, ở trên chúng ta đã sử dụng Query để R rồi, vậy CUD sẽ dùng gì? Với GraphQL, những điều này được thực hiện bằng cách sử dụng Mutations.
Để tạo 1 cái gì trong GraphQL bạn cứ nhớ phải định nghĩa type input và output trong file schema rồi mới định nghĩa thực thi cụ thể trong resolvers như sau:
const { gql } = require('apollo-server-express')
const schema = gql`
type Book {
id: ID
name: String
}
# ROOT TYPE
type Query {
books: [Book]
book(id: ID!): Book
}
type Mutation {
createBook(name: String, description: String, authorId: ID!): Book
}
`
module.exports = schema
Bạn thấy cái định nghĩa mới là createBook có các args gồm (name, description, authorId ) và kết quả trả ra là 1 Kiểu dữ liệu Book ( được định nghĩa ở trên rồi nè)
Cập nhật thêm file resolvers cho hàm mới là createBook như sau:
const Book = require('../models/Book')
const resolvers = {
// QUERY
Query: {
books: async (parent, args, context) => await Book.find({}),
book: async (parent, args, context) => {
const { id } = args // id truyền vào từ câu query theo định nghĩa của file /schema/schema.js: book(id: ID!): Book
return await Book.findById(id)
},
},
// MUTATION để CUD data
Mutation: {
createBook: async (parent, args, context) => {
/// bạn thêm logic lưu data từ trong args định nghĩa trong schema như sau
const { name, description, authorId } = args
return await (new Book({ name, description, authorId })).save()
}
}
}
module.exports = resolvers
Vào background query thử thì thấy được vầy nè:
III. Phần 3 - Cập nhật đầy đủ cho book và author
edit lại code cho schema
const { gql } = require('apollo-server-express')
const schema = gql`
type Book {
id: ID
name: String,
description: String,
author: Author
}
type Author {
id: ID!
name: String
age: Int
books: [Book]
}
# ROOT TYPE
type Query {
books: [Book]
book(id: ID!): Book
authors: [Author]
author(id: ID!): Author
}
type Mutation {
createBook(name: String, description: String, authorId: ID!): Book,
createAuthor(name: String, age: Int): Author
}
`
module.exports = schema
Cập nhật định nghĩa file resolvers như sau:
const Author = require('../models/Author')
const Book = require('../models/Book')
const resolvers = {
// QUERY
Query: {
books: async (parent, args, context) => await Book.find({}),
book: async (parent, args, context) => {
const { id } = args // id truyền vào từ câu query theo định nghĩa của file /schema/schema.js: book(id: ID!): Book
return await Book.findById(id)
},
authors: async (parent, args, context) => await Author.find({}),
author: async (parent, args, context) => {
const { id } = args // id truyền vào từ câu query theo định nghĩa của file /schema/schema.js: book(id: ID!): Book
return await Author.findById(id)
},
},
// MUTATION để CUD data
Mutation: {
createBook: async (parent, args, context) => {
/// bạn thêm logic lưu data từ trong args định nghĩa trong schema như sau
const { name, description, authorId } = args
return await (new Book({ name, description, authorId })).save()
},
createAuthor: async (parent, args, context) => {
/// bạn thêm logic lưu data từ trong args định nghĩa trong schema như sau
const { name, age } = args
return await (new Author({ name, age })).save()
}
}
}
module.exports = resolvers
Tới đây là bạn có thể tạo tác giả sau đó lấy danh sách tác giả như sau:
À có vẻ work hết rồi. ơ mà trong type Author gồm: { id, name, age, books }. Nhưng khi query books thì lỗi banh nóc nhà luôn 😄
Lý do bạn chưa ref cho Author cách lấy books như nào nên nó ngu không biết lấy và kết quả là văng lỗi. Bạn cần phải định nghĩa thêm cho resolvers như sau:
const Author = require('../models/Author')
const Book = require('../models/Book')
const resolvers = {
// QUERY
Query: {
books: async (parent, args, context) => await Book.find({}),
book: async (parent, args, context) => {
const { id } = args // id truyền vào từ câu query theo định nghĩa của file /schema/schema.js: book(id: ID!): Book
return await Book.findById(id)
},
authors: async (parent, args, context) => await Author.find({}),
author: async (parent, args, context) => {
const { id } = args // id truyền vào từ câu query theo định nghĩa của file /schema/schema.js: book(id: ID!): Book
return await Author.findById(id)
},
},
Book: {
author: async (parent, args, context) => {
// data object hiện tại là parent tương ứng là 1 book
return await Author.findById(parent.authorId)
}
},
// chỉ cho resolver khi cần lấy danh sách books từ 1 object là author thì phải làm sao
Author: {
books: async (parent, args, context) => {
// data object hiện tại là parent tương ứng là 1 author
return await Book.find({ authorId: parent.id }) || []
}
},
// MUTATION để CUD data
Mutation: {
createBook: async (parent, args, context) => {
/// bạn thêm logic lưu data từ trong args định nghĩa trong schema như sau
const { name, description, authorId } = args
return await (new Book({ name, description, authorId })).save()
},
createAuthor: async (parent, args, context) => {
/// bạn thêm logic lưu data từ trong args định nghĩa trong schema như sau
const { name, age } = args
return await (new Author({ name, age })).save()
}
}
}
module.exports = resolvers
Kết quả sau khi bạn lưu tác giả theo sách thì được như vầy:
Trong phần 2 mình sẽ hướng dẫn tiếp theo phần:
bạn theo dõi bài viết: Cách kết nối client react với graphQL nodejs