Protocol Buffers works great with TypeScript, giving you type safety and efficient serialization. Whether you're building Node.js backends, web applications, or React Native apps, protobuf provides a solid foundation for data exchange.
This guide shows you how to use Protocol Buffers in TypeScript projects using ts-proto, the most popular TypeScript protobuf generator.
What You'll Need
- •Node.js: Version 14 or higher
- •TypeScript: Version 4.x or higher
- •Protocol Buffer Compiler: We'll install this next
Step 1: Install Dependencies
Create a new TypeScript project:
mkdir protobuf-ts-example cd protobuf-ts-example npm init -y npm install typescript ts-node @types/node --save-dev npx tsc --init
Install protobuf dependencies:
npm install ts-proto protobufjs npm install protoc-gen-ts-proto --save-dev
Install protoc compiler:
# On macOS brew install protobuf # On Windows choco install protoc # On Linux sudo apt install protobuf-compiler
Step 2: Set Up Project Structure
Create the following structure:
protobuf-ts-example/ ├── package.json ├── tsconfig.json ├── proto/ │ └── person.proto ├── src/ │ ├── generated/ │ └── index.ts
Step 3: Create Your .proto File
Create proto/person.proto
:
syntax = "proto3"; package person; message Person { string name = 1; int32 id = 2; string email = 3; repeated string phone_numbers = 4; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 5; bool is_active = 6; } message AddressBook { repeated Person people = 1; }
Step 4: Generate TypeScript Code
Add a generation script to package.json
:
{ "scripts": { "generate": "protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./src/generated --ts_proto_opt=esModuleInterop=true proto/*.proto", "start": "ts-node src/index.ts" } }
Run the generator:
npm run generate
Step 5: Use Protocol Buffers in TypeScript
Create src/index.ts
:
import { Person, AddressBook } from './generated/person'; import * as fs from 'fs'; // Create a Person const person: Person = { name: 'John Doe', id: 1234, email: 'john.doe@example.com', phoneNumbers: ['555-1234', '555-5678'], phones: [ { number: '555-9999', type: Person_PhoneType.MOBILE, }, ], isActive: true, }; console.log('Created Person:'); console.log(`Name: ${person.name}`); console.log(`ID: ${person.id}`); console.log(`Email: ${person.email}`); // Serialize to bytes const bytes = Person.encode(person).finish(); console.log(`\nSerialized to ${bytes.length} bytes`); // Deserialize from bytes const decoded = Person.decode(bytes); console.log('\nDeserialized Person:'); console.log(`Name: ${decoded.name}`); console.log(`Active: ${decoded.isActive}`); // Save to file fs.writeFileSync('person.bin', bytes); console.log('\nSaved to person.bin'); // Read from file const fileData = fs.readFileSync('person.bin'); const fromFile = Person.decode(fileData); console.log(`Read from file: ${fromFile.name}`); // Create AddressBook const addressBook: AddressBook = { people: [ person, { name: 'Jane Smith', id: 5678, email: 'jane@example.com', phoneNumbers: [], phones: [], isActive: true, }, ], }; console.log(`\nAddress book has ${addressBook.people.length} people`); // Convert to JSON const json = Person.toJSON(person); console.log('\nJSON representation:'); console.log(JSON.stringify(json, null, 2)); // Parse from JSON const fromJson = Person.fromJSON({ name: 'Bob', id: 9999, email: 'bob@example.com', isActive: true, }); console.log(`\nCreated from JSON: ${fromJson.name}`);
Step 6: Run Your Application
npm start
Expected output:
Created Person: Name: John Doe ID: 1234 Email: john.doe@example.com Serialized to 78 bytes Deserialized Person: Name: John Doe Active: true Saved to person.bin Read from file: John Doe Address book has 2 people
Common Operations
Type-Safe Access
// TypeScript knows all fields and types const name: string = person.name; const id: number = person.id; const phones: Person_PhoneNumber[] = person.phones;
Partial Updates
const updated: Person = { ...person, email: 'newemail@example.com', };
Validate with Zod (Optional)
// Generate with --ts_proto_opt=useExactTypes=false // to get runtime validation schemas
Stream Encoding
import { Writer } from 'protobufjs/minimal'; const writer = new Writer(); Person.encode(person, writer); const bytes = writer.finish();
Best Practices for TypeScript
Use Strict Type Checking
Enable strict: true
in tsconfig.json to catch type errors at compile time.
Don't Edit Generated Files
Generated TypeScript files are recreated each time. Add src/generated/
to your .gitignore.
Use ts-proto Options
Customize generation with options like esModuleInterop
,useOptionals
, andoneof=unions
.
Using with Express
import express from 'express'; import { Person } from './generated/person'; const app = express(); app.use(express.raw({ type: 'application/x-protobuf' })); app.post('/person', (req, res) => { const person = Person.decode(req.body); console.log('Received:', person.name); const response = Person.encode(person).finish(); res.type('application/x-protobuf').send(response); }); app.listen(3000);
Common Issues
Issue: protoc-gen-ts_proto not found
Solution: Use the full path in your script:./node_modules/.bin/protoc-gen-ts_proto
Issue: Module not found errors
Solution: Make sure to install both ts-proto
andprotobufjs
.
Issue: Type errors in generated code
Solution: Update ts-proto to the latest version:npm update ts-proto
Related Tools
Conclusion
Protocol Buffers with TypeScript gives you the best of both worlds: efficient binary serialization and strong type safety. The ts-proto library generates clean, idiomatic TypeScript code that feels natural to use.
This combination works great for Node.js services, web applications, and even browser-based apps where you need efficient data exchange with type safety.