จากบทความที่แล้ว การใช้งาน GraphQL ใน Back-End ได้ทดลองทำ API โดยใช้ GraphQLในการเรียกดูข้อมูลผู้ใช้งานและเพิ่มผู้ใช้งานซึ่งเก็บอยู่ใน Local File แล้ว ในบทความนี้จะทำการต่อยอดจากบทความที่แล้วในเรื่องของการใช้ GraphQL ร่วมกับ PostgreSQL โดยจะเชื่อมต่อ API กับฐานข้อมูล ซึ่งในบทความนี้จะใช้ Sequelize ในการแปลง Database ให้เป็น Object หากผู้อ่านไม่คุ้นเคยกับ Sequelize ลองศึกษาได้จากบทความจากลิงค์นี้ครับ Node JS กับ Sequelize 101
พื้นฐานที่ต้องมีก่อนจะทำตามบทความนี้
- PostgreSQL ควรมีฐานข้อมูลสำหรับทดสอบการเชื่อมต่อ และควรสามารถใช้ pgAdmin หรือโปรแกรมอื่นในการดูฐานข้อมูลได้
- Sequelize ควรเข้าใจการสร้าง model เบื้องต้นมาก่อน
- GraphQL ควรเข้าใจพื้นฐานของ graphQL เช่น การสร้าง query และ mutation
สร้าง model และย้ายข้อมูลจากไฟล์ไปสู่ฐานข้อมูล
ขั้นแรกเราจะย้ายข้อมูล User ซึ่งมีข้อมูล id firstName lastName email และ password จากเดิมที่ใช้ในบทความครั้งที่แล้วซึ่งอยู่ในไฟล์ MOCK_DATA.json ไปไว้ในฐานข้อมูล
การย้ายข้อมูลนี้ทำได้หลายวิธี แต่ในครั้งนี้เราจะทำการสร้าง model โดยใช้ Sequelize ก่อน จากจะทำการเชื่อมต่อ model กับฐานข้อมูล หากฐานข้อมูลเราไม่มีตารางที่สอดคล้องกับ model โปรแกรมก็จะสร้างตารางให้เราโดยอัตโนมัติ ซึ่งจะมีขั้นตอนดังนี้
- install package ต่างๆ ที่เกี่ยวข้อง
- สร้าง model
- เชื่อมต่อระหว่าง model กับ ฐานข้อมูล
- ย้ายข้อมูลจากไฟล์ไปสู่ฐานข้อมูล
เริ่มจาก install package ที่ต้องใช้เพิ่มในบทความนี้ ได้แก่ sequelize pg และ pg-hstore ซึ่ง package 2 ตัวหลัง ใช้จัดการฐานข้อมูล PostgreSQL
npm install --save pg pg-hstore sequelize
เมื่อลง package เสร็จใน package.json ควรจะปรากฎ package ที่ลง ดังรูป
สร้างโฟล์เดอร์ใหม่ชื่อ db เพื่อให้เก็บไฟล์ต่างๆ ที่ใช้ในการจัดการฐานข้อมูล ได้แก่ config.js db.js และ seed.js โดย
- ไฟล์ config จะเก็บข้อมูลต่างๆ เช่น username password ที่ใช้ในการเชื่อมต่อกับฐานข้อมูล
- ไฟล์ db.js จะทำหน้าที่เชื่อมต่อฐานข้อมูลกับ model ที่สร้าง
- ไฟล์ seed.js จะเป็นส่วนที่ใช้สำหรับย้ายข้อมูลจากไฟล์ MOCK_DATA.json ไปไว้ในฐานข้อมูล
จากนั้นในโฟล์เดอร์ db สร้างโฟล์เดอร์ชื่อ model เป็นเก็บ model ที่จะสร้างโดยใช้ sequelize จากนั้นสร้างไฟล์ชื่อ user.js สำหรับสร้าง model ดังนั้นภายในโฟล์เดอร์ db จะมีไฟล์ต่างๆ ดังรูป
สร้าง model ในไฟล์ /db/model/user.js
module.exports = ( sequelize , Sequelize ) => {
//sequelize.define(modelName, attributes, options)
const users = sequelize.define(
'users', //ชื่อของ model
{
//attributes ต่างๆ ซึ่งจะตั้งให้สอดคล้องกับ User ในที่นี้คือ id firstName lastName email password
id: { type: Sequelize.INTEGER(), primaryKey: true, autoIncrement: true, field: 'id' },//ตั้งเป็น primary key และกำหนดให้มีค่าเพิ่มขึ้นเองเมื่อมีข้อมูลเพิ่ม
firstName: { type: Sequelize.STRING(50), allowNull: false, field: 'firstName' },
lastName: { type: Sequelize.STRING(50), allowNull: false, field: 'lastName' },
email: { type: Sequelize.STRING(50), allowNull: false, field: 'email' },
password: { type:Sequelize.STRING(50), allowNull: false, field: 'password' },
},
{
// ส่วนของ option ในที่นี้จะปิดการเพิ่ม timestamp
//หากไม่กำหนดค่าในส่วนนี้เป็น false sequelize จะเป็น attributes createdAt และ updatedAt ให้โดยอัตโนมัติ
timestamps: false,
createdAt: false,
updatedAt: false,
tableName: 'users' // กำหนดชื่อตาราง
}
);
return users;
}
JavaScriptกลับมาที่ไฟล์ /db/db.js สร้างการเชื่อมต่อระหว่าง model กับ ฐานข้อมูล
const dbConfig = require('./config'); // เรียกค่าต่างๆ ที่ใช้ในการเชื่อมต่อกับฐานข้อมูล
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(
dbConfig.dbName, // ชื่อของฐานข้อมูลเช่น 'BornToDevDB'
dbConfig.username, //'youusername'
dbConfig.password, {
host: dbConfig.host,
/*dialect => one of 'mysql' | 'postgres' | 'sqlite' | 'mariadb' | 'mssql' | 'db2' | 'snowflake' | 'oracle' */
//ในบทความนี้ใช้ dialect: 'postgres'
dialect: dbConfig.dialect
});
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.users = require('./model/user')( sequelize , Sequelize );
module.exports = db;
JavaScriptสำหรับไฟล์ /db/config.js
module.exports = {
'username': 'yourUserName',
'password': ' yourUserPassword',
'host':'localhost',
'dbName': 'BornToDevDB',
'dialect': 'postgres',
}
JavaScriptในไฟล์ /db/ seed.js ทำการเชื่อมต่อ model กับฐานข้อมูล โดยใช้คำสั่ง sequelize.sync() ดังนี้
const db = require("./db");
const users = db.users;
db.sequelize.sync();
JavaScriptจากนั้นใน terminal พิมพ์คำสั่ง node ./pathToYourFile/seed.js โปรแกรมจะสร้างตารางชื่อ usres ให้ซึ่งควรจะปรากฎข้อความดังนี้ใน terminal
เมื่อไปตรวจสอบในฐานข้อมูลควรพบตารางชื่อ users ที่เป็นตารางว่างๆ ซึ่งมีหัวตารางตาม attribute ตามที่กำหนดไว้ใน model
เพิ่มคำสั่งใน /db/ seed.js เพื่อย้ายข้อมูลทั้งหมดในไฟล์ MOCK_DATA.json ไปสู่ตาราง users ในฐานข้อมูล ดังนี้
const userData = require("../MOCK_DATA.json");
//สร้างฟังก์ชัน เพื่อย้ายข้อมูลทั้งหมดไปสู่ฐานข้อมูล
async function bulkInsertUsers(usersData) {
try {
const createdUsers = await users.bulkCreate(usersData);
// ถ้าทำการย้ายข้อมูลสำเร็จให้รายงานจำนวนข้อมูลที่ย้ายใน console
console.log(
"Bulk insert successful:",
createdUsers.length,
"users inserted."
);
} catch (error) {
console.error("Error inserting users:", error);
}
}
//เรียกใช้ฟังก์ชัน
bulkInsertUsers(userData);
JavaScriptจากนั้นใน terminal พิมพ์คำสั่ง node ./pathToYourFile/seed.js อีกครั้งเพื่อทำการย้ายข้อมูล ซึ่งควรปรากฎข้อความจำนวนข้อมูลที่ถูกย้าย ดังรูป
ทำการตรวจสอบในฐานข้อมูลซึ่งควรมีข้อมูลปรากฎในตาราง users ที่เราสร้าง
แก้ไขไฟล์ index.js เพื่อใช้งานกับฐานข้อมูล
อ้างอิงจากโค้ดที่ให้ไว้จากบทความครั้งที่แล้ว สิ่งที่เราจะต้องแก้ไขจะมีดังนี้
- เพิ่มส่วนการเรียกใช้ฐานข้อมูลและ model
- ตัดส่วนการเรียกใช้ไฟล์ข้อมูลเดิม
- แก้ไขโค้ดส่วน resolver หรือ ส่วนที่จัดการข้อมูลก่อนส่งข้อมูลกลับไปให้ผู้ใช้งาน ของ query และ mutation
สำหรับส่วนที่ 1 และ 2 สามารถแก้ไขได้ดังนี้
ส่วนที่ 3 จะแยกเป็นส่วนของ query และ mutation เราจะเริ่มจากส่วนของ query ซึ่งจะมี 2 ฟังก์ชันคือ getAllUsers กับ getUser ดังรูป
ด้านซ้ายเป็นโค้ดเดิมที่ข้อมูลเก็บอยู่ในไฟล์ เราเรียกใช้ข้อมูลผ่านตัวแปร userData ซึ่งเป็น array ของ object จะแตกต่างจากด้านขวามือซึ่งข้อมูลถูกเก็บในฐานข้อมูล ดังนั้นในการทำงานของโปรแกรม เมื่อได้รับคำสั่งจะผู้ใช้งาน โปรแกรมจะต้องส่งคำสั่งไปยังฐานข้อมูล และรอจนกว่าฐานข้อมูลตอบกลับมา จึงจะดำเนินการตอบกลับผู้ใช้งาน ในการจะสั่งให้โปรแกรมรอจนกว่าฐานข้อมูลตอบกลับมาแล้วค่อยดำเนินการขั้นต่อไปนั้น จะต้องใช้ await ซึ่งจะต้องอยู่ภายใต้ฟังก์ชันที่ประกาศเป็น async
ในส่วนของ mutation แก้ไขดังนี้
จะเห็นว่าด้านขวามือที่ข้อมูลถูกเก็บในฐานข้อมูล นอกจากใช้ฟังก์ชัน async และ await แล้ว ไม่จำเป็นต้องใช้ข้อมูล id ซึ่งจะแตกต่างจากด้านซ้ายมือ เนื่องจากตอนสร้าง model เราได้กำหนดให้ id มีค่าเพิ่มขึ้นทีละ 1 โดยอัตโนมัติ ดังนั้นเมื่อมีข้อมูลใหม่เพิ่มเข้ามาให้ฐานข้อมูล ฐานข้อมูลจะกำหนด id ให้กับข้อมูลใหม่นั้นโดยอัตโมมัติ
เมื่อแก้ไขเสร็จแล้วไฟล์ index.js จะเป็นดังนี้
const express = require("express");
const app = express();
const port = 3000;
const graphql = require("graphql");
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLInt,
GraphQLString,
GraphQLList,
} = require("graphql");
const { graphqlHTTP } = require("express-graphql");
const db = require('./db/db');
const users = db.users;
const { where } = require("sequelize");
db.sequelize.sync();
const UserType = new GraphQLObjectType({
name: "User",
fields: {
id: { type: GraphQLInt },
firstName: { type: GraphQLString },
lastName: { type: GraphQLString },
email: { type: GraphQLString },
password: { type: GraphQLString },
},
});
app.use(express.json());
const RootQuery = new GraphQLObjectType({
name: "RootQueryType",
description: "getALlUsers,getUser(id)",
fields: {
getAllUsers: {
type: new GraphQLList(UserType),
async resolve(parent, args) {
info = await users.findAll();
return info;
},
description: "get all users from database",
},
getUser: {
type: UserType,
args: {
id: { type: GraphQLInt },
},
async resolve(parent, args) {
info = await users.findOne({
where: {id:args.id}
})
return info;
},
description: "get user by id ",
},
},
});
const Mutation = new GraphQLObjectType({
name: "Mutation",
fields: {
createUser: {
type: UserType,
args: {
firstName: { type: GraphQLString },
lastName: { type: GraphQLString },
email: { type: GraphQLString },
password: { type: GraphQLString },
},
async resolve(parent, args) {
const newRecord = await users.create({
firstName: args.firstName,
lastName: args.lastName,
email: args.email,
password: args.password,
});
return newRecord;
},
},
},
});
const schema = new GraphQLSchema({ query: RootQuery, mutation: Mutation });
app.use(
"/graphql",
graphqlHTTP({
schema,
graphiql: true,
})
);
app.listen(port, () => {
console.log("Listening on port %d", port);
});
JavaScriptสรุป
ความแตกต่างระหว่างการใช้ GrpahQL กับข้อมูลที่เก็บอยู่ใน Local File ในบทความที่แล้ว กับการใช้ร่วมกับฐานข้อมูลในบทความนี้นั้น มีความแตกต่างกันที่การใช้งาน GraphQL ร่วมกับฐานข้อมูลจะต้องสร้าง Model ซึ่งสอดคล้องกับตารางในฐานข้อมูล จากนั้นจึงใช้ Model เป็น Input สำหรับการ Query และ Mutation เท่านั้น ซึ่งก็ไม่ได้ซับซ้อนอย่างที่คิดเลยใช่ไหมครับ