0Pricing
Node.js Backend Development Bootcamp · Pelajaran

Mendefinisikan Layanan dan Pesan dengan Protobuf IDL

Tulis kontrak .proto dan hasilkan stub client serta server yang aman tipe darinya.

Mendefinisikan Layanan dan Pesan dengan Protobuf IDL adalah pelajaran Node.js Backend Development Bootcamp gratis di CoddyKit. Ini adalah pelajaran 1 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Node.js Backend Development Bootcamp, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Node.js Backend Development Bootcamp mencakup 4 pelajaran total.

Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.

Why an IDL?

In a gRPC microservice, the contract comes before the code. You describe your data and your RPC methods in a language-neutral Interface Definition Language (IDL) called .proto, then generate type-safe stubs for Node.js, Go, Java, and more from that single file.

  • One .proto file is the single source of truth shared by client and server.
  • Protocol Buffers (protobuf) is both the IDL and the binary wire format.
  • You never hand-write the serialization code — the compiler does it.

This lesson shows how to write .proto contracts and turn them into JavaScript client and server stubs.

Anatomy of a .proto file

Every modern .proto file starts by declaring the syntax version and a package. The package namespaces your symbols so two services can both define a User without colliding.

  • syntax = "proto3"; — always use proto3 for new services.
  • package — logical namespace, maps to a JS object path after generation.
  • The file groups message (data shapes) and service (RPC methods).
// user.proto
syntax = "proto3";

package users.v1;

// A data shape sent over the wire
message User {
  string id = 1;
  string email = 2;
  bool active = 3;
}

Messages and field numbers

A message is a record of typed fields. The number after the = is the field tag, not a default value. Tags are how protobuf identifies each field on the binary wire — they must be unique within a message and must never change once deployed.

  • Field names can be renamed freely; tags must stay stable for backward compatibility.
  • Tags 1-15 use a single byte, so reserve them for your most frequent fields.
  • Scalar types: string, bool, int32, int64, double, bytes.
message Product {
  string id = 1;          // tag 1 (1 byte on the wire)
  string name = 2;
  int32 stock = 3;
  double price = 4;
  bool discontinued = 5;
}

proto3 to JavaScript type mapping

When you load a .proto with @grpc/proto-loader, each protobuf type maps to a JavaScript value. Knowing the mapping avoids surprises at runtime.

  • string, bool, int32, float, double map to JS string, boolean, number.
  • int64 / uint64 map to a string by default in JS (numbers can exceed Number.MAX_SAFE_INTEGER).
  • bytes becomes a Buffer.
  • Unset proto3 scalars come back as their zero value ("", 0, false) — they are never undefined on the wire.
// What a decoded User object looks like in Node.js
const user = {
  id: 'u_123',      // string -> string
  email: '',         // unset string -> '' (zero value)
  active: false,     // unset bool -> false
  loginCount: '0'    // int64 -> string, not number!
};

console.log(typeof user.loginCount); // 'string'

Defining a service

A service block lists the RPC methods clients can call. Each rpc takes exactly one request message and returns exactly one response message. Wrapping request/response in dedicated messages (rather than passing bare scalars) lets you add fields later without breaking the contract.

  • Method names are PascalCase by convention.
  • Always define a request and a response message per method, even if one is empty.
message GetUserRequest { string id = 1; }
message GetUserResponse { User user = 1; }

service UserService {
  // unary: one request -> one response
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc CreateUser(CreateUserRequest) returns (User);
}

The four RPC kinds

The stream keyword on either side of an rpc declaration controls the streaming model. The same IDL keyword decides whether your generated handler receives a single value or a stream.

  • Unary: rpc Get(Req) returns (Res) — one in, one out.
  • Server streaming: returns (stream Res) — server pushes many.
  • Client streaming: (stream Req) — client uploads many.
  • Bidirectional: (stream Req) returns (stream Res).
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc ListOrders(ListOrdersRequest) returns (stream Order);
  rpc ImportOrders(stream Order) returns (ImportSummary);
  rpc LiveOrders(stream OrderEvent) returns (stream OrderEvent);
}

Enums, repeated, and nested messages

Beyond scalars, protobuf gives you composite shapes for real-world data.

  • enum — a closed set of values; the first member must be 0 and is the default.
  • repeated — an ordered list; decodes to a JS Array.
  • Nested messages model structured sub-records.

Prefix enum members to avoid name clashes, since enum values share the enclosing scope.

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0; // required zero default
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_SHIPPED = 2;
}

message Order {
  string id = 1;
  OrderStatus status = 2;
  repeated LineItem items = 3; // -> JS Array
}

message LineItem {
  string sku = 1;
  int32 qty = 2;
}

Loading the contract in Node.js

In Node you generate stubs at runtime with @grpc/proto-loader plus @grpc/grpc-js. The loader parses the .proto and loadPackageDefinition turns it into a navigable JS object keyed by your package path.

  • keepCase: true preserves field names exactly as written in the proto.
  • longs: String keeps 64-bit ints safe as strings.
  • The package path users.v1 becomes proto.users.v1.
const protoLoader = require('@grpc/proto-loader');
const grpc = require('@grpc/grpc-js');

const pkgDef = protoLoader.loadSync('user.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const proto = grpc.loadPackageDefinition(pkgDef);
const UserService = proto.users.v1.UserService;

Implementing the server stub

The generated UserService gives you a service definition you register handlers against. Each unary handler receives (call, callback); call.request is your decoded request message, and you reply via the Node-style callback(err, response).

  • Return a gRPC status by passing an error with a code from grpc.status.
  • The response object must match the response message fields.
const server = new grpc.Server();

server.addService(UserService.service, {
  GetUser(call, callback) {
    const { id } = call.request;
    const user = db.find(id);
    if (!user) {
      return callback({
        code: grpc.status.NOT_FOUND,
        message: `User ${id} not found`,
      });
    }
    callback(null, { user });
  },
});

Calling from the client stub

The same generated UserService is also a client constructor. You instantiate it with a target address and credentials, then call methods directly. Each unary call takes the request object and a Node-style callback.

  • credentials.createInsecure() for local/dev; use TLS in production.
  • The request and response are plain JS objects matching the proto messages.
const client = new UserService(
  'localhost:50051',
  grpc.credentials.createInsecure()
);

client.GetUser({ id: 'u_123' }, (err, res) => {
  if (err) {
    console.error(err.code, err.message);
    return;
  }
  console.log(res.user.email);
});

Evolving the contract safely

A contract is only useful if it can change without breaking deployed clients. proto3 makes additive evolution safe when you follow a few rules.

  • Add new fields with new, never-before-used tag numbers — old clients ignore them.
  • Never reuse or renumber tags; mark removed tags with reserved.
  • Renaming a field is wire-compatible (tag is what matters), but breaks JSON/text usage.
  • Use reserved for both removed tag numbers and names to prevent accidental reuse.
message User {
  reserved 4, 5;              // retired tags, never reuse
  reserved "phone";          // retired field name
  string id = 1;
  string email = 2;
  bool active = 3;
  string display_name = 6;   // safe additive change
}

Quick Check

You need to remove the deprecated phone field (tag 4) from a deployed User message. What is the correct, backward-compatible way to do it?

Recap

You learned to design and consume gRPC contracts with Protobuf IDL:

  • A .proto file with syntax = "proto3" and a package is the single source of truth for client and server.
  • message defines typed records; the number after each field is its stable wire tag, not a default.
  • service + rpc declare methods; the stream keyword selects unary, server-, client-, or bidirectional streaming.
  • enum, repeated, and nested messages model richer data; int64 decodes to a JS string.
  • In Node, @grpc/proto-loader + grpc.loadPackageDefinition generate both the server service and the client constructor.
  • Evolve contracts additively and protect retired tags/names with reserved.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Mendefinisikan Layanan dan Pesan dengan Protobuf IDL” gratis?

Ya — teks lengkap “Mendefinisikan Layanan dan Pesan dengan Protobuf IDL” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Node.js Backend Development Bootcamp, upgrade ke CoddyKit PRO. Kursus Node.js Backend Development Bootcamp mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Mendefinisikan Layanan dan Pesan dengan Protobuf IDL”?

Tulis kontrak .proto dan hasilkan stub client serta server yang aman tipe darinya. Kamu berlatih Node.js Backend Development Bootcamp dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai Node.js Backend Development Bootcamp?

Tidak diperlukan pengalaman sebelumnya. Node.js Backend Development Bootcamp di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 1 dari 4.

Berapa lama pelajaran “Mendefinisikan Layanan dan Pesan dengan Protobuf IDL” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran Node.js Backend Development Bootcamp ini?

Ya. Setiap pelajaran Node.js Backend Development Bootcamp menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Mendefinisikan Layanan dan Pesan dengan Protobuf IDL
  2. RPC Streaming Unary, Server, Client, dan Dua Arah
  3. Interceptor, Deadline, dan Metadata
  4. Evolusi Proto dan Kompatibilitas Mundur
← Kembali ke Node.js Backend Development Bootcamp