正在加载,请稍候…

gRPC with Node.js: Build High-Performance APIs Beyond REST

Implement gRPC services in Node.js — protocol buffers, streaming RPCs, interceptors, error handling, load balancing, and comparing gRPC vs REST.

Why gRPC?

gRPC offers binary serialization (faster than JSON), contract-first development via Protocol Buffers, and native support for streaming — things REST can't match.

Define Your Service

// user.proto
syntax = "proto3";

package user;

service UserService {
  // Unary RPC
  rpc GetUser (GetUserRequest) returns (UserResponse);
  
  // Server streaming
  rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
  
  // Client streaming
  rpc CreateUsers (stream CreateUserRequest) returns (CreateUsersResponse);
  
  // Bidirectional streaming
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

message GetUserRequest {
  string id = 1;
}

message UserResponse {
  string id = 1;
  string name = 2;
  string email = 3;
  int64 created_at = 4;
}

message ListUsersRequest {
  int32 page = 1;
  int32 page_size = 2;
  string filter = 3;
}

Server Implementation

import grpc from '@grpc/grpc-js';
import protoLoader from '@grpc/proto-loader';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

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

const UserProto = grpc.loadPackageDefinition(packageDef).user;

const userService = {
  getUser: async (call, callback) => {
    const { id } = call.request;
    try {
      const user = await db.users.findById(id);
      if (!user) {
        return callback({
          code: grpc.status.NOT_FOUND,
          message: 'User not found',
        });
      }
      callback(null, user);
    } catch (err) {
      callback({ code: grpc.status.INTERNAL, message: err.message });
    }
  },
  
  listUsers: async (call) => {
    const { page, page_size, filter } = call.request;
    try {
      const users = await db.users.findMany({ page, pageSize: page_size, filter });
      for (const user of users) {
        call.write(user);  // Stream each user
      }
      call.end();
    } catch (err) {
      call.destroy(err);
    }
  },
};

const server = new grpc.Server();
server.addService(UserProto.UserService.service, userService);
server.bindAsync(
  '0.0.0.0:50051',
  grpc.ServerCredentials.createInsecure(),
  (err, port) => {
    if (err) throw err;
    console.log('gRPC server running on port', port);
  }
);

Client Implementation

const client = new UserProto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure()
);

// Unary call
const getUser = (id) => new Promise((resolve, reject) => {
  client.getUser({ id }, (err, response) => {
    if (err) reject(err);
    else resolve(response);
  });
});

// Server streaming
function streamUsers(filter) {
  const stream = client.listUsers({ page: 1, page_size: 100, filter });
  
  return new Promise((resolve, reject) => {
    const users = [];
    stream.on('data', (user) => users.push(user));
    stream.on('end', () => resolve(users));
    stream.on('error', reject);
  });
}

Interceptors (Middleware)

// Server interceptor for logging
function loggingInterceptor(options, nextCall) {
  return new grpc.ServerInterceptingCall(nextCall(options), {
    start: function(metadata, listener, next) {
      const start = Date.now();
      next(metadata, {
        ...listener,
        onReceiveMessage: function(message, next) {
          next(message);
        },
        onSendMessage: function(message, next) {
          console.log(`RPC ${options.method_definition.path}: ${Date.now() - start}ms`);
          next(message);
        }
      });
    }
  });
}

gRPC vs REST

Feature gRPC REST
Protocol HTTP/2 HTTP/1.1+
Payload Binary (Protobuf) JSON/XML
Streaming Native SSE/WebSocket
Type safety Compile-time Runtime
Browser support Limited Universal
Performance ~10x faster Standard

TLS in Production

const credentials = grpc.credentials.createSsl(
  fs.readFileSync('ca-cert.pem'),
  fs.readFileSync('client-key.pem'),
  fs.readFileSync('client-cert.pem')
);