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. Compare it with JSON serialization to see the benefits.
What You'll Need
- •Node.js: Version 14 or higher (download from nodejs.org)
- •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: 'Maria Garcia',
id: 1234,
email: '[email protected]',
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: '[email protected]',
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: '[email protected]',
isActive: true,
});
console.log(`\nCreated from JSON: ${fromJson.name}`);Step 6: Run Your Application
npm start
Expected output:
Created Person: Name: Maria Garcia ID: 1234 Email: [email protected] Serialized to 78 bytes Deserialized Person: Name: Maria Garcia Active: true Saved to person.bin Read from file: Maria Garcia 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: '[email protected]',
};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.