Skip to main content
0

เขียน gRPC API ครั้งแรกใน Node.js

วันนี้เราจะมาลองใช้ gRPC ทำระบบ CRUD ง่าย ๆ บน Node.js กันครับ เพื่อให้เข้าใจว่า gRPC ทำงานอย่างไร ให้เพื่อน ๆ สามารถนำไปต่อยอดกับงานของตัวเองได้ง่าย ๆ กัน

gRPC คืออะไร?

gRPC คือเฟรมเวิร์กสำหรับสร้าง API ที่พัฒนาโดย Google โดยหลักการทำงานของ gRPC จะไม่ใช้การส่งข้อมูลแบบ JSON เหมือน REST API แต่จะใช้ Protocol Buffers (Protobuf) ซึ่งมีขนาดข้อมูลเล็กกว่า และยังใช้ HTTP/2 ที่เร็วกว่า HTTP/1.1 ของ REST API อีกด้วย จึงทำให้การส่ง Request – Response แต่ละครั้งมี Latency ต่ำ และทำงานได้รวดเร็วขึ้น

จุดเด่นของ HTTP/2 ที่ gRPC ใช้

  • HTTP/2 รองรับ multiplexing → สามาถส่งไปหลาย Requests พร้อมกันช่วยลดเวลาในการรอแต่ละ requests
  • ต่างจาก HTTP/1.1 ที่ใช้ใน REST API ทั่วไป → ต้องส่งข้อมูลทีละไฟล์ถ้าส่งไฟล์เยอะ ๆ ก็จะเสียเวลาในการส่งเยอะมาก ๆ
  • เหมาะกับระบบใหญ่ ๆ ที่มี Service หลายตัว → ด้วย HTTP/2 จะทำให้เราส่งข้อมูลระหว่าง Service แต่ละตัวไปมาได้เร็วขึ้น

Protobuf (Protocol Buffers) คืออะไร?

Protobuf เป็น IDL (Interface Definition Language) ที่ gRPC ใช้กำหนดรูปแบบข้อมูลของ API ว่าแต่ละ endpoint ต้องรับ-ส่งข้อมูลชนิดใด ข้อดีของ Protobuf คือสามารถ serialize ข้อมูลให้อยู่ในรูป binary ทำให้ขนาดเล็กกว่า JSON ส่งข้อมูลได้เร็วและประหยัด bandwidth มากขึ้น

ที่มาของภาพ : https://medium.com/@business.agency.koloc/demystifying-protobuf-a-detailed-guide-wallarm-42ef59d15a08

ตัวอย่างการใช้งาน gRPC กับ Microservices

ตัวอย่างที่ gRPC ทำได้ดี เช่น การสื่อสารระหว่าง Backend Service หรือการใช้กับ API Gateway ที่มีข้อมูลวิ่งไปมาปริมาณมาก และต้องการความเร็วสูง

เรามาเริ่มติดตั้ง Project กันเลย

npm init -y 
npm install @grpc/grpc-js @grpc/proto-loader
JavaScript

สร้างไฟล์ proto กัน

สร้างโฟลเดอร์ proto/ และไฟล์ book.proto เพื่อเขียนสเปค API

syntax = "proto3";

package book;

message bookInfo {
  int32 bookId = 1;
  string bookName = 2;
  string author = 3;
}

message buyingDone {
  string message = 1;
}

service testBook {
  // เปลี่ยน method เป็น TestBook
  rpc TestBook (bookInfo) returns (buyingDone);
}
JavaScript

ต่อมาเราจะมาสร้างไฟล์ service.js ที่จะเป็นตัวจัดการฟังก์ชัน API ของเราก่อนส่งไปให้ index.js เรียกใช้

function TestBook(call, callback) {
    const { bookId, bookName, author } = call.request;
    const message = `📚 Book ID ${bookId}: "${bookName}" by ${author} is now purchased!`;
    callback(null, { message });
  }
  
  module.exports = {
    TestBook,
  };
JavaScript

จากนั้นเราจะมาสร้าง Server ของในไฟล์ index.js

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
const bookService = require('./service');

const PROTO_PATH = path.join(__dirname, 'proto/book.proto');

// โหลด proto แบบ dynamic
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true
});

const proto = grpc.loadPackageDefinition(packageDefinition).user;

const server = new grpc.Server();

// map method SayHello ไปที่ฟังก์ชัน bookService.TestBook
server.addService(proto.testBook.service, {
    TestBook: bookService.TestBook,
});

const PORT = '0.0.0.0:50051';
server.bindAsync(PORT, grpc.ServerCredentials.createInsecure(), (err, port) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(`📘 gRPC Book Server running at ${PORT}`);
});
JavaScript

โครงสร้าง Project

project/
├── node_modules/
├── proto/
│   └── book.protoไฟลproto 
├── service.jsจัดการ logic CRUD แยกไว
├── index.jsไฟลเริ gRPC server
├── package.json
├── package-lock.json
JavaScript

จากนั้นเราจะลองมาทดสอบยิง API ผ่าน Postman กันครับว่า API ของเราทำงานได้หรือเปล่า

node index.js 
JavaScript

ทดสอบ API ของเราด้วย Postman

เปิด Postman → กด New → เลือก gRPC

จากนั้นใส่ port เป็น localhost:50051

อัปโหลดไฟล์ book.proto

เลือก Method: TestBook ใส่ข้อมูล แล้วกด Invoke ได้เลย

Implement CRUD

ทำการเขียน API เส้นอื่น ๆ เข้าไปในไฟล์ .Proto

syntax = "proto3";

package book;

// Message ข้อมูลหนังสือ
message Book {
  int32 book_id = 1;
  string book_name = 2;
  string author = 3;
}

// Request/Response: Create
message CreateBookRequest {
  Book book = 1;
}
message CreateBookResponse {
  Book book = 1;
}

// Request/Response: Get
message GetBookRequest {
  int32 book_id = 1;
}
message GetBookResponse {
  Book book = 1;
}

// Request/Response: Update
message UpdateBookRequest {
  Book book = 1;
}
message UpdateBookResponse {
  Book book = 1;
}

// Request/Response: Delete
message DeleteBookRequest {
  int32 book_id = 1;
}
message DeleteBookResponse {
  bool success = 1;
}

// Response: List
message ListBooksRequest {}
message ListBooksResponse {
  repeated Book books = 1;
}

// Service
service BookService {
  rpc CreateBook (CreateBookRequest) returns (CreateBookResponse);
  rpc GetBook (GetBookRequest) returns (GetBookResponse);
  rpc UpdateBook (UpdateBookRequest) returns (UpdateBookResponse);
  rpc DeleteBook (DeleteBookRequest) returns (DeleteBookResponse);
  rpc ListBooks (ListBooksRequest) returns (ListBooksResponse);
}
JavaScript

เพิ่มฟังก์ชันใน Service.js

let books = [];
let idCounter = 1;

function CreateBook(call, callback) {
  const book = call.request.book;
  book.book_id = idCounter++;
  books.push(book);
  callback(null, { book });
}

function GetBook(call, callback) {
  const book = books.find(b => b.book_id === call.request.book_id);
  book
    ? callback(null, { book })
    : callback({ code: 5, message: "Book not found" });
}

function UpdateBook(call, callback) {
  const updatedBook = call.request.book;
  const index = books.findIndex(b => b.book_id === updatedBook.book_id);
  if (index !== -1) {
    books[index] = updatedBook;
    callback(null, { book: updatedBook });
  } else {
    callback({ code: 5, message: "Book not found" });
  }
}

function DeleteBook(call, callback) {
  const index = books.findIndex(b => b.book_id === call.request.book_id);
  if (index !== -1) {
    books.splice(index, 1);
    callback(null, { success: true });
  } else {
    callback({ code: 5, message: "Book not found" });
  }
}

function ListBooks(call, callback) {
  callback(null, { books });
}

module.exports = {
  CreateBook,
  GetBook,
  UpdateBook,
  DeleteBook,
  ListBooks,
};
JavaScript

เพิ่ม Endpoint index.js

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
const bookService = require('./service');

const PROTO_PATH = path.join(__dirname, 'proto/book.proto');

const packageDef = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const proto = grpc.loadPackageDefinition(packageDef).book;

const server = new grpc.Server();

server.addService(proto.BookService.service, {
  CreateBook: bookService.CreateBook,
  GetBook: bookService.GetBook,
  UpdateBook: bookService.UpdateBook,
  DeleteBook: bookService.DeleteBook,
  ListBooks: bookService.ListBooks,
});

const PORT = '0.0.0.0:50051';
server.bindAsync(PORT, grpc.ServerCredentials.createInsecure(), (err, port) => {
  if (err) return console.error(err);
  console.log(`📚 gRPC Book CRUD Server running at ${PORT}`);
});
JavaScript

ทดสอบ CRUD ผ่าน Postman

gRPC ใน Postman จะสามารถเลือก Method ทั้งหมดจาก .proto ที่อัปโหลดไปมาลองทดสอบเรียก CreateBook, GetBook, UpdateBook, DeleteBook, ListBooks กันหน่อย

มาทดสอบกันเริ่มจาก CreateBook

GetBook

UpdateBook

DeleteBook

แล้วสุดท้ายเราจะลอง List รายชื่อหนังสือ โดยที่ก่อน List ให้ลองสร้างรายการหนังสือสักสองรอบดู

🎉 และนี่คือการสร้างระบบ CRUD ด้วย gRPC + Node.js ครบทุกขั้นตอน! เหมาะสำหรับต่อยอดไปใช้กับระบบจริง เช่น Microservices หรือ API Gateway ได้เลยครับผม

0

แนะนำสำหรับคุณ

คัดลอกลิงก์สำเร็จ
Close Menu

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

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

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

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

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

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

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

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