gRPC для TypeScript: библиотеки и примеры
gRPC — это современный высокопроизводительный фреймворк для удалённого вызова процедур (RPC), разработанный Google. Он использует Protocol Buffers (protobuf) в качестве языка определения интерфейсов и формата сериализации данных. В TypeScript gRPC реализуется через пакеты @grpc/grpc-js (для серверной и клиентской логики) и protobufjs (для работы с .proto файлами). В этой статье мы разберём ключевые библиотеки, настройку окружения и напишем рабочий пример на TypeScript.
Ключевые библиотеки для gRPC в TypeScript
@grpc/grpc-js— чистая JavaScript-реализация gRPC (без C++ аддонов). Поддерживает все основные функции: унарные вызовы, серверную и клиентскую стриминг, двунаправленный стриминг.protobufjs— библиотека для загрузки, парсинга и генерации классов из.protoфайлов. Позволяет динамически создавать сообщения и сервисы.@grpc/proto-loader— вспомогательный пакет, который упрощает загрузку proto-файлов и автоматически генерирует TypeScript-типы на основе определений.ts-protoc-gen— генератор кода для компилятораprotoc. Позволяет сгенерировать строго типизированные классы и интерфейсы из.protoфайлов.
Для большинства проектов достаточно связки @grpc/grpc-js + @grpc/proto-loader. Если требуется максимальная типобезопасность, используют ts-protoc-gen или protobufjs с генерацией статических типов.
Установка зависимостей
Создайте новый проект и установите необходимые пакеты:
mkdir grpc-typescript-example
cd grpc-typescript-example
npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
npm install -D typescript @types/node
npx tsc --init
Создание proto-файла
Определим простой сервис для работы с пользователями. Создайте файл user.proto:
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
}
message GetUserRequest {
int32 id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
Реализация gRPC-сервера на TypeScript
Создадим файл server.ts:
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import path from 'path';
const PROTO_PATH = path.join(__dirname, 'user.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const userProto = grpc.loadPackageDefinition(packageDefinition) as any;
// Имитация базы данных
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
function getUser(
call: grpc.ServerUnaryCall<{ id: number }, any>,
callback: grpc.sendUnaryData<any>
) {
const user = users.find(u => u.id === call.request.id);
if (!user) {
return callback({
code: grpc.status.NOT_FOUND,
message: 'User not found',
});
}
callback(null, user);
}
function listUsers(call: grpc.ServerWritableStream<{ page: number; limit: number }, any>) {
const page = call.request.page || 1;
const limit = call.request.limit || 10;
const start = (page - 1) * limit;
const end = start + limit;
const paginatedUsers = users.slice(start, end);
paginatedUsers.forEach(user => call.write(user));
call.end();
}
function main() {
const server = new grpc.Server();
server.addService(userProto.UserService.service, {
GetUser: getUser,
ListUsers: listUsers,
});
const address = '0.0.0.0:50051';
server.bindAsync(
address,
grpc.ServerCredentials.createInsecure(),
(error, port) => {
if (error) {
console.error(error);
return;
}
console.log(`gRPC server running on ${address}`);
server.start();
}
);
}
main();