สวัสดีครับสำหรับวันนี้เรามาอยู่กับบทความในหัวข้อที่เกี่ยวกับ “GraphQL” กันบ้างครับ เชื่อว่าคนที่ทำ API (Application Programming Interface) อยู่ต้องเจอคำว่า “GraphQL” กันอยู่บ้าง แล้วพอลองใช้งานจะเจอกับสองคำศัพท์หลัก ๆ นั่นก็คือ Query กับ Mutation เป็นพื้นฐานแรก ๆ เลยที่มือใหม่หัดใช้ GraphQL จะต้องรู้ สองคำนี้คืออะไรมาหาคำตอบกันเลย!
ก่อนจะพูดถึง Query และ Mutation เรามาทำความรู้จักกับ GraphQL กันแบบไว ๆ กันก่อนดีกว่าครับ โดย GraphQL (Graph Query Language) คือภาษาที่ใช้ในการดึงและจัดการข้อมูลระหว่าง Client และ Server การใช้งานเราจะต้องมี “GraphQL Server” ที่ทำหน้าที่ประมวลผล Request จากฝั่ง Client แล้วส่งข้อมูลกลับไปตามรูปแบบที่ Client ระบุ ซึ่งข้อดีหลัก ๆ ของ GraphQL คือมันทำให้เราสามารถเรียกข้อมูลมาเฉพาะเท่าที่ต้องการ ตามคอนเซปที่ถูกเรียกว่า request exactly what you need, nothing more, nothing less
“Client เป็นคนกำหนดเองว่าจะดึงข้อมูลอะไรบ้างจาก Server” แทนที่จะให้ Server กำหนดตายตัวเหมือนกับ REST API หากใครต้องการเริ่มต้นใช้งาน GraphQL ใน Back-End ครั้งแรก แอดมินแนะนำบทความจากคุณ “sorajit.a” ได้ที่ลิงก์นี้ได้เลย www.borntodev.com/2024/04/10/การใช้งาน-graphql-ใน-back-end/
Query ใน GraphQL
โดย Query จะเป็นคำสั่งฝั่ง “ดึงข้อมูล” ออกมาจาก GraphQL Server โดยเราสามารถเขียนโครงสร้างของ Query เพื่อให้ได้ข้อมูลตามที่ต้องการ หน้าตาจะเป็นแบบนี้
query {
getUser(id: 1) {
name
email
}
}
JavaScriptจากตัวอย่างนี้เป็นการดีงข้อมูล user ที่มี id เท่ากับ 1 เอามาเฉพาะฟิลด์ที่เป็น name และ email เท่านั้น อย่างอื่นไม่ต้องเอามา เราสามารถตัวอย่างนี้ เป็นการดึงข้อมูลของผู้ใช้ที่มี id เท่ากับ 1 แล้วให้ส่งเฉพาะฟิลด์ name กับ email กลับมา ไม่เอาอย่างอื่น
จะเห็นได้ว่าตรง id: 1 คือเราสามารถใส่ parameter เข้าไปได้ เพื่อกำหนดเงื่อนไขในการดึงข้อมูลมาสำหรับข้อมูลที่เฉพาะเจาะจง
Mutation ใน GraphQL
มาในฝั่ง Mutation กันบ้าง สำหรับฝั่งนี้จะเป็นการแก้ไข เพิ่ม หรือลบข้อมูลในระบบ โดยการทำงานนั้นหากเราต้องการเพิ่มข้อมูลก็เขียนเป็น mutation { … } ถ้าอยากแก้ไขข้อมูล หรือลบก็ทำผ่าน Mutation ได้เช่นกัน ตัวอย่างหน้าตาของ mutation
mutation {
createUser(
name: "Aef BorntoDev",
email: "aef@borntodev.com"
) {
id
name
email
}
}
JavaScriptสรุปความเหมือน/แตกต่างระหว่าง Query vs. Mutation
ตัวอย่างการทำงาน
เพื่อให้เห็นภาพมากขึ้นเดี๋ยวผมจะพาไปสร้าง GraphQL ต่อกับ MySQL โดยใช้ Node.js พร้อมกับ Apollo Server โดย Apollo Server เป็น เซิร์ฟเวอร์ GraphQL
เริ่มจากทำการสร้างโปรเจกต์ใหม่
mkdir graphql-mysql-direct-sql
cd graphql-mysql-direct-sql
npm init -y
JavaScriptติดตั้ง package ที่ใช้กับโปรเจกต์
npm install apollo-server graphql mysql2
JavaScriptapollo-server: เซิร์ฟเวอร์ GraphQL
graphql: ไลบรารีหลักของ GraphQL
mysql2: ไดรเวอร์สำหรับเชื่อมต่อกับ MySQL
สร้างไฟล์ db.js เพื่อกำหนดการเชื่อมต่อกับฐานข้อมูล MySQL โดยใช้ mysql2 (ปกติแล้วค่าในการเชื่อมต่อจะไม่เก็บในโค้ดนะ จะต้องแยกไฟล์ให้หรือเก็บไว้ใน .env ให้เรียบร้อย)
// db.js
const mysql = require('mysql2/promise');
// สร้างการเชื่อมต่อกับฐานข้อมูล MySQL
const pool = mysql.createPool({
host: 'localhost',
user: 'username', // เปลี่ยนเป็นชื่อผู้ใช้ของคุณ
password: 'password', // เปลี่ยนเป็นรหัสผ่านของคุณ
database: 'demo_graphql_db', // เปลี่ยนเป็นชื่อฐานข้อมูลของคุณ
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
module.exports = pool;
JavaScriptหลังจากนั้นทำการกำหนด Schema สำหรับ GraphQL โดยการสร้างไฟล์ schema.js เพื่อกำหนดประเภทของข้อมูล (Type Definitions) และ Mutation ต่าง ๆ สำหรับ API ของเรา
// schema.js
const { gql } = require('apollo-server');
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
}
type Query {
users: [User]
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User
updateUser(id: ID!, name: String, email: String): User
deleteUser(id: ID!): Boolean
}
`;
module.exports = typeDefs;
JavaScriptหลังจากนั้นกำหนด Resolvers สำหรับ Query และ Mutation
สร้างไฟล์ resolvers.js เพื่อกำหนดการทำงานของแต่ละ Query และ Mutation โดยใช้ SQL ตรงๆ ผ่าน mysql2
// resolvers.js
const pool = require('./db');
const resolvers = {
Query: {
// ดึงข้อมูลผู้ใช้ทั้งหมด
users: async () => {
const [rows] = await pool.query('SELECT id, name, email FROM users');
return rows;
},
// ดึงข้อมูลผู้ใช้เฉพาะเจาะจงด้วย ID
user: async (_, { id }) => {
const [rows] = await pool.query('SELECT id, name, email FROM users WHERE id = ?', [id]);
return rows[0] || null;
},
},
Mutation: {
// สร้างผู้ใช้ใหม่
createUser: async (_, { name, email }) => {
const [result] = await pool.query('INSERT INTO users (name, email) VALUES (?, ?)', [name, email]);
const newUserId = result.insertId;
const [rows] = await pool.query('SELECT id, name, email FROM users WHERE id = ?', [newUserId]);
return rows[0];
},
// แก้ไขข้อมูลผู้ใช้
updateUser: async (_, { id, name, email }) => {
// ตรวจสอบว่าผู้ใช้มีอยู่จริงหรือไม่
const [existingUser] = await pool.query('SELECT id, name, email FROM users WHERE id = ?', [id]);
if (existingUser.length === 0) {
throw new Error('User not found');
}
// กำหนดค่าที่จะอัพเดต
const updatedName = name !== undefined ? name : existingUser[0].name;
const updatedEmail = email !== undefined ? email : existingUser[0].email;
// อัพเดตข้อมูลในฐานข้อมูล
await pool.query('UPDATE users SET name = ?, email = ? WHERE id = ?', [updatedName, updatedEmail, id]);
// ดึงข้อมูลผู้ใช้ที่อัพเดตแล้วมาแสดง
const [rows] = await pool.query('SELECT id, name, email FROM users WHERE id = ?', [id]);
return rows[0];
},
// ลบผู้ใช้
deleteUser: async (_, { id }) => {
const [result] = await pool.query('DELETE FROM users WHERE id = ?', [id]);
return result.affectedRows > 0;
},
},
};
module.exports = resolvers;
JavaScriptการใช้ pool.query ใช้สำหรับรันคำสั่ง SQL โดยใช้การเชื่อมต่อแบบ Pooling เพื่อประสิทธิภาพที่ดีขึ้น ส่วน ? ใน SQL เป็นการใช้ parameterized queries เพื่อป้องกัน SQL Injection สุดท้ายเราก็จัดการผลลัพธ์ หลังจากทำ INSERT หรือ UPDATE จะดึงข้อมูลผู้ใช้ที่สร้างหรืออัพเดตแล้วมาแสดงผล หลังจากนั้น สร้างไฟล์ index.js เพื่อรันเซิร์ฟเวอร์ GraphQL
// index.js
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const pool = require('./db');
const server = new ApolloServer({
typeDefs,
resolvers,
context: {
// คุณสามารถเพิ่ม context ต่าง ๆ ได้ที่นี่ เช่น authentication
},
});
// รันเซิร์ฟเวอร์
server.listen({ port: 4000 }).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
JavaScriptก่อนที่จะรันเซิร์ฟเวอร์ เราต้องแน่ใจก่อนว่าเราสร้างฐานข้อมูลใน MySQL และมีตาราง users ตามที่กำหนดใน Schema หากยังไม่มี สามารถสร้างได้ดังนี้
-- ตัวอย่างการสร้างฐานข้อมูล
CREATE DATABASE database_name;
-- เลือกใช้ฐานข้อมูล
USE database_name;
-- สร้างตาราง users
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
JavaScriptหมายเหตุ: เปลี่ยน ‘database_name’ ให้ตรงกับชื่อฐานข้อมูลที่คุณกำหนดในไฟล์ db.js
เมื่อพร้อมแล้วเราก็กลับไปที่ Terminal แล้วรัน
node index.js
JavaScriptถ้าทุกอย่างถูกต้อง จะเห็นข้อความ
ตอนนี้เราสามารถเข้าถึง GraphQL Playground ได้ที่ http://localhost:4000/ เพื่อทดสอบ Query และ Mutation
แล้วเราลองเขียน Query ไป
จะเห็นได้ว่าตอนนี้ยังไม่มีข้อมูลในฐานข้อมูล เราก็ลองเป็นจาก Query เป็น Mutation สำหรับเพิ่มผู้ใช้ใหม่เข้าไปในระบบ
ลอง Query มาอีกครั้ง
Mutation: แก้ไขข้อมูลผู้ใช้
Mutation: ลบผู้ใช้
และนี่ก็จะเป็นตัวอย่างการใช้ Query และ Mutation ในการขอ หรือ เปลี่ยนแปลงข้อมูลนั่นเอง แอดหวังว่าบทความนี้จะมีประโยชน์สำหรับมือใหม่สาย GraphQL นะครับ 🧡