JavaScript/TypeScript Language Support
JavaScript/TypeScript Language Support
Section titled “JavaScript/TypeScript Language Support”Status: ✅ Full Support
Example: examples/js-example/
JavaScript/TypeScript support provides multiple output formats and RPC options for modern web development.
Available Plugins
Section titled “Available Plugins”Plugin | Description | Generated Files |
---|---|---|
protoc-gen-js | CommonJS messages | *_pb.js , *_pb.d.ts |
protoc-gen-es | ES modules | *.js , *.d.ts |
protoc-gen-connect-es | Connect-ES RPC | *_connect.js |
protoc-gen-grpc-web | gRPC-Web client | *_grpc_web_pb.js |
protoc-gen-twirp_js | Twirp RPC | *_twirp.js |
Configuration
Section titled “Configuration”Basic Configuration
Section titled “Basic Configuration”languages.js = { enable = true; outputPath = "src/proto";};
Full Configuration
Section titled “Full Configuration”languages.js = { enable = true; outputPath = "src/proto"; packageName = "@myorg/proto"; options = [ "import_style=commonjs" "binary" ];
# Modern ECMAScript modules es = { enable = true; options = [ "target=ts" # Generate TypeScript "import_extension=.js" # ES module extensions "json_types=true" # JSON type definitions ]; };
# Connect-ES for type-safe RPC connect = { enable = true; options = [ "target=ts" "import_extension=.js" ]; };
# gRPC-Web for browser compatibility grpcWeb = { enable = true; options = [ "import_style=typescript" "mode=grpcwebtext" "format=text" ]; };
# Twirp RPC framework twirp = { enable = true; options = ["lang=typescript"]; };};
Proto Example
Section titled “Proto Example”syntax = "proto3";
package example.v1;
message User { string id = 1; string name = 2; string email = 3; int32 age = 4; repeated string tags = 5; map<string, string> metadata = 6;
oneof status { ActiveStatus active = 7; InactiveStatus inactive = 8; }}
message ActiveStatus { string since = 1; string activity = 2;}
message InactiveStatus { string reason = 1; string until = 2;}
service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse); rpc ListUsers(ListUsersRequest) returns (ListUsersResponse); rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); rpc StreamUsers(StreamUsersRequest) returns (stream User);}
message GetUserRequest { string id = 1;}
message GetUserResponse { User user = 1;}
message ListUsersRequest { int32 page_size = 1; string page_token = 2; repeated string tags = 3;}
message ListUsersResponse { repeated User users = 1; string next_page_token = 2; int32 total_count = 3;}
message CreateUserRequest { User user = 1;}
message CreateUserResponse { User user = 1;}
message StreamUsersRequest { repeated string tags = 1;}
Generated Code Usage
Section titled “Generated Code Usage”import { UserService } from "./proto/example/v1/example_connect.js";import { User, ActiveStatus } from "./proto/example/v1/example_pb.js";import { createPromiseClient } from "@connectrpc/connect";import { createConnectTransport } from "@connectrpc/connect-web";
// Create transportconst transport = createConnectTransport({ baseUrl: "https://api.example.com", credentials: "include", interceptors: [ (next) => async (req) => { console.log(`Calling ${req.method.name}`); const res = await next(req); console.log(`Response received`); return res; }, ],});
// Create typed clientconst client = createPromiseClient(UserService, transport);
async function main() { try { // Create a new user with typed data const user = new User({ id: "1", name: "John Doe", email: "john@example.com", age: 30, tags: ["developer", "typescript"], metadata: { department: "Engineering", location: "Remote" }, status: { case: "active", value: new ActiveStatus({ since: "2024-01-01", activity: "coding" }) } });
const createResponse = await client.createUser({ user: user, });
console.log("Created user:", createResponse.user);
// Get the user const getResponse = await client.getUser({ id: "1", });
console.log("Retrieved user:", getResponse.user);
// List users with pagination const listResponse = await client.listUsers({ pageSize: 10, pageToken: "", tags: ["developer"], });
console.log(`Found ${listResponse.users.length} users`);
// Stream users for await (const user of client.streamUsers({ tags: ["active"] })) { console.log("Streamed user:", user.name); } } catch (error) { console.error("RPC failed:", error); }}
main();
import { UserServiceClient } from "./proto/example/v1/example_grpc_web_pb";import { GetUserRequest, CreateUserRequest, User } from "./proto/example/v1/example_pb";
// Create gRPC-Web clientconst client = new UserServiceClient("https://api.example.com");
// Enable CORS credentialsconst metadata = { "authorization": "Bearer token" };
// Create userconst createRequest = new CreateUserRequest();const user = new User();user.setId("1");user.setName("John Doe");user.setEmail("john@example.com");user.setAge(30);user.setTagsList(["developer", "typescript"]);
const metadataMap = user.getMetadataMap();metadataMap.set("department", "Engineering");
createRequest.setUser(user);
client.createUser(createRequest, metadata, (err, response) => { if (err) { console.error("Error:", err.message); return; } console.log("Created user:", response.getUser()?.getName());});
// Get user with promisesconst getRequest = new GetUserRequest();getRequest.setId("1");
const promise = new Promise((resolve, reject) => { client.getUser(getRequest, metadata, (err, response) => { if (err) { reject(err); } else { resolve(response); } });});
promise .then((response: any) => { console.log("User:", response.getUser()?.toObject()); }) .catch((err) => { console.error("Failed:", err); });
// Stream usersconst streamRequest = new StreamUsersRequest();streamRequest.setTagsList(["active"]);
const stream = client.streamUsers(streamRequest, metadata);
stream.on("data", (user) => { console.log("Streamed:", user.getName());});
stream.on("error", (err) => { console.error("Stream error:", err);});
stream.on("end", () => { console.log("Stream ended");});
import { User, ActiveStatus } from "./proto/example/v1/example_pb.js";import { create, toBinary, fromBinary, toJson, fromJson } from "@bufbuild/protobuf";
// Create messages with ES modulesconst user = create(UserSchema, { id: "1", name: "John Doe", email: "john@example.com", age: 30, tags: ["developer"], metadata: { role: "senior" }, status: { case: "active", value: { since: "2024-01-01", activity: "coding" } }});
// Binary serializationconst bytes = toBinary(UserSchema, user);console.log(`Binary size: ${bytes.length} bytes`);
// Binary deserializationconst decoded = fromBinary(UserSchema, bytes);console.log("Decoded:", decoded);
// JSON serializationconst json = toJson(UserSchema, user);console.log("JSON:", JSON.stringify(json, null, 2));
// JSON deserializationconst fromJsonUser = fromJson(UserSchema, json);console.log("From JSON:", fromJsonUser);
// Type-safe field accessif (user.status.case === "active") { console.log("Active since:", user.status.value.since);}
// Working with repeated fieldsuser.tags.push("javascript", "protobuf");
// Working with mapsuser.metadata["team"] = "Platform";
// Iterate map entriesfor (const [key, value] of Object.entries(user.metadata)) { console.log(`${key}: ${value}`);}
import { UserService } from "./proto/example/v1/example_twirp";import { User } from "./proto/example/v1/example_pb";
// Create Twirp clientconst client = new UserService.Client("https://api.example.com");
// Set custom headersclient.setHeaders({ "Authorization": "Bearer token"});
async function useTwirp() { try { // Create user const user: User = { id: "1", name: "John Doe", email: "john@example.com", age: 30, tags: ["developer"], metadata: { location: "Remote" }, active: { since: "2024-01-01", activity: "coding" } };
const createResponse = await client.CreateUser({ user: user });
console.log("Created:", createResponse.user);
// Get user const getResponse = await client.GetUser({ id: "1" }); console.log("Retrieved:", getResponse.user);
// List users const listResponse = await client.ListUsers({ pageSize: 10, pageToken: "", tags: ["developer"] });
console.log("Users:", listResponse.users);
} catch (error) { if (error.code) { // Twirp error console.error(`Twirp error ${error.code}: ${error.msg}`); } else { // Network error console.error("Network error:", error); } }}
useTwirp();
import { createServer } from "@connectrpc/connect";import { createNodeServer } from "@connectrpc/connect-node";import { UserService } from "./proto/example/v1/example_connect";import { User } from "./proto/example/v1/example_pb";
// In-memory storageconst users = new Map<string, User>();
// Implement serviceconst userService = { async getUser(request) { const user = users.get(request.id); if (!user) { throw new Error("User not found"); } return { user }; },
async createUser(request) { users.set(request.user!.id, request.user!); return { user: request.user! }; },
async listUsers(request) { const allUsers = Array.from(users.values());
// Filter by tags const filtered = request.tags.length > 0 ? allUsers.filter(u => u.tags.some(t => request.tags.includes(t)) ) : allUsers;
// Pagination const start = request.pageToken ? parseInt(request.pageToken) : 0; const end = start + request.pageSize; const page = filtered.slice(start, end);
return { users: page, nextPageToken: end < filtered.length ? end.toString() : "", totalCount: filtered.length }; },
async *streamUsers(request) { for (const [_, user] of users) { if (request.tags.length === 0 || user.tags.some(t => request.tags.includes(t))) { yield user; // Simulate real-time updates await new Promise(resolve => setTimeout(resolve, 1000)); } } }} satisfies typeof UserService;
// Create and start serverconst server = createNodeServer({ services: [ { service: UserService, implementation: userService } ], address: "localhost:8080",});
await server.start();console.log("Server listening on", server.address);
Build Tools Integration
Section titled “Build Tools Integration”TypeScript Configuration
Section titled “TypeScript Configuration”{ "compilerOptions": { "target": "ES2022", "module": "ES2022", "moduleResolution": "node", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "paths": { "@/proto/*": ["./src/proto/*"] } }, "include": ["src/**/*"], "exclude": ["src/proto/**/*_pb.js"]}
Package.json Scripts
Section titled “Package.json Scripts”{ "scripts": { "proto": "bufrnix", "build": "tsc", "dev": "tsx watch src/index.ts", "test": "vitest" }, "dependencies": { "@connectrpc/connect": "^1.0.0", "@connectrpc/connect-web": "^1.0.0", "@bufbuild/protobuf": "^1.0.0" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.0.0", "tsx": "^4.0.0", "vitest": "^1.0.0" }}
Browser Usage
Section titled “Browser Usage”Vite Configuration
Section titled “Vite Configuration”export default { optimizeDeps: { include: [ "@connectrpc/connect", "@connectrpc/connect-web", "@bufbuild/protobuf", ], },};
React Example
Section titled “React Example”import { useEffect, useState } from "react";import { createPromiseClient } from "@connectrpc/connect";import { createConnectTransport } from "@connectrpc/connect-web";import { UserService } from "@/proto/example/v1/example_connect";import { User } from "@/proto/example/v1/example_pb";
const transport = createConnectTransport({ baseUrl: import.meta.env.VITE_API_URL,});
const client = createPromiseClient(UserService, transport);
export function UserList() { const [users, setUsers] = useState<User[]>([]); const [loading, setLoading] = useState(true);
useEffect(() => { client .listUsers({ pageSize: 20, pageToken: "" }) .then((response) => { setUsers(response.users); setLoading(false); }) .catch((error) => { console.error("Failed to load users:", error); setLoading(false); }); }, []);
if (loading) return <div>Loading...</div>;
return ( <ul> {users.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li> ))} </ul> );}
Best Practices
Section titled “Best Practices”- Use Connect-ES: Preferred for new projects - type-safe, modern API
- Enable TypeScript: Use
target=ts
for better type safety - ES Modules: Use
.js
extensions for proper ES module support - Error Handling: Use proper error types from Connect/gRPC
- Streaming: Use async iterators for efficient streaming
- Tree Shaking: ES modules enable better bundle optimization
Try the Example
Section titled “Try the Example”cd examples/js-examplenix developnpm installnpm run buildnpm start
Troubleshooting
Section titled “Troubleshooting”Module Resolution
Section titled “Module Resolution”For ES modules, ensure proper extensions:
// Correctimport { User } from "./proto/example_pb.js";
// Incorrectimport { User } from "./proto/example_pb";
CORS Issues
Section titled “CORS Issues”For browser clients, configure server CORS:
const transport = createConnectTransport({ baseUrl: "https://api.example.com", credentials: "include", // For cookies // OR headers: { Authorization: "Bearer token", },});
TypeScript Paths
Section titled “TypeScript Paths”Configure path mapping in tsconfig.json:
{ "compilerOptions": { "paths": { "@/proto/*": ["./src/proto/*"] } }}
Complete Flake Configuration Example
Section titled “Complete Flake Configuration Example”{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
bufrnix = {
url = "github:conneroisu/bufrnix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
nixpkgs,
flake-utils,
bufrnix,
...
}:
flake-utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.default = pkgs.mkShell {
packages = [
pkgs.nodejs
pkgs.nodePackages.typescript
];
};
packages = {
default = bufrnix.lib.mkBufrnixPackage {
inherit pkgs;
config = {
root = ./.;
protoc = {
sourceDirectories = ["./proto"];
includeDirectories = ["./proto"];
files = ["./proto/example/v1/example.proto"];
};
languages.js = {
enable = true;
outputPath = "proto/gen/js";
packageName = "example-proto";
# Modern JavaScript with ECMAScript modules
es = {
enable = true;
};
};
};
};
};
});
}