Skip to main content
0
Back-End DeveloperDevInitไม่มีหมวดหมู่

การใช้ GraphQL ร่วมกับ PostgreSQL

จากบทความที่แล้ว การใช้งาน GraphQL ใน Back-End ได้ทดลองทำ API โดยใช้ GraphQLในการเรียกดูข้อมูลผู้ใช้งานและเพิ่มผู้ใช้งานซึ่งเก็บอยู่ใน Local File แล้ว ในบทความนี้จะทำการต่อยอดจากบทความที่แล้วในเรื่องของการใช้ GraphQL ร่วมกับ PostgreSQL โดยจะเชื่อมต่อ API กับฐานข้อมูล ซึ่งในบทความนี้จะใช้ Sequelize ในการแปลง Database ให้เป็น Object หากผู้อ่านไม่คุ้นเคยกับ Sequelize ลองศึกษาได้จากบทความจากลิงค์นี้ครับ Node JS กับ Sequelize 101

พื้นฐานที่ต้องมีก่อนจะทำตามบทความนี้

  1. PostgreSQL ควรมีฐานข้อมูลสำหรับทดสอบการเชื่อมต่อ และควรสามารถใช้ pgAdmin หรือโปรแกรมอื่นในการดูฐานข้อมูลได้
  2. Sequelize ควรเข้าใจการสร้าง model เบื้องต้นมาก่อน
  3. GraphQL ควรเข้าใจพื้นฐานของ graphQL เช่น การสร้าง query และ mutation

สร้าง model และย้ายข้อมูลจากไฟล์ไปสู่ฐานข้อมูล

ขั้นแรกเราจะย้ายข้อมูล User ซึ่งมีข้อมูล id firstName lastName email และ password จากเดิมที่ใช้ในบทความครั้งที่แล้วซึ่งอยู่ในไฟล์ MOCK_DATA.json ไปไว้ในฐานข้อมูล

การย้ายข้อมูลนี้ทำได้หลายวิธี แต่ในครั้งนี้เราจะทำการสร้าง model โดยใช้ Sequelize ก่อน จากจะทำการเชื่อมต่อ model กับฐานข้อมูล หากฐานข้อมูลเราไม่มีตารางที่สอดคล้องกับ model โปรแกรมก็จะสร้างตารางให้เราโดยอัตโนมัติ ซึ่งจะมีขั้นตอนดังนี้

  1. install package ต่างๆ ที่เกี่ยวข้อง
  2. สร้าง model
  3. เชื่อมต่อระหว่าง model กับ ฐานข้อมูล
  4. ย้ายข้อมูลจากไฟล์ไปสู่ฐานข้อมูล

เริ่มจาก 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 เพื่อใช้งานกับฐานข้อมูล

อ้างอิงจากโค้ดที่ให้ไว้จากบทความครั้งที่แล้ว สิ่งที่เราจะต้องแก้ไขจะมีดังนี้

  1. เพิ่มส่วนการเรียกใช้ฐานข้อมูลและ model
  2. ตัดส่วนการเรียกใช้ไฟล์ข้อมูลเดิม
  3. แก้ไขโค้ดส่วน 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 เท่านั้น ซึ่งก็ไม่ได้ซับซ้อนอย่างที่คิดเลยใช่ไหมครับ

อ้างอิง

sorajit.a

Author sorajit.a

More posts by sorajit.a

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • คุกกี้ที่จำเป็น
    เปิดใช้งานตลอด

    ประเภทของคุกกี้มีความจำเป็นสำหรับการทำงานของเว็บไซต์ เพื่อให้คุณสามารถใช้ได้อย่างเป็นปกติ และเข้าชมเว็บไซต์ คุณไม่สามารถปิดการทำงานของคุกกี้นี้ในระบบเว็บไซต์ของเราได้
    รายละเอียดคุกกี้

  • คุกกี้สำหรับการติดตามทางการตลาด

    ประเภทของคุกกี้ที่มีความจำเป็นในการใช้งานเพื่อการวิเคราะห์ และ นำเสนอโปรโมชัน สินค้า รวมถึงหลักสูตรฟรี และ สิทธิพิเศษต่าง ๆ คุณสามารถเลือกปิดคุกกี้ประเภทนี้ได้โดยไม่ส่งผลต่อการทำงานหลัก เว้นแต่การนำเสนอโปรโมชันที่อาจไม่ตรงกับความต้องการ
    รายละเอียดคุกกี้

บันทึกการตั้งค่า