Skip to content

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.

PluginDescriptionGenerated Files
protoc-gen-jsCommonJS messages*_pb.js, *_pb.d.ts
protoc-gen-esES modules*.js, *.d.ts
protoc-gen-grpc-webgRPC-Web client*_grpc_web_pb.js
protoc-gen-twirp_jsTwirp RPC*_twirp.js
languages.js = {
enable = true;
outputPath = "src/proto";
};
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
];
};
# 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"];
};
};
🆕 New Feature

Control exactly which proto files JavaScript/TypeScript processes using files and additionalFiles:

Use Case: Frontend needs Google API annotations for REST mapping, but backend doesn’t.

# Global files (shared by all languages)
protoc.files = [
"./proto/common/v1/types.proto"
"./proto/common/v1/status.proto"
];
languages = {
# JavaScript: Extend global files with frontend-specific files
js = {
enable = true;
additionalFiles = [
"./proto/api/v1/user_api.proto" # Public REST APIs
"./proto/api/v1/auth_api.proto" # Authentication
"./proto/google/api/annotations.proto" # HTTP annotations
"./proto/google/api/http.proto" # REST definitions
];
es.enable = true;
};
# Go: Use only internal services (excludes Google annotations)
go = {
enable = true;
files = [
"./proto/common/v1/types.proto"
"./proto/internal/v1/user_service.proto"
# No Google annotations - prevents linting errors
];
grpc.enable = true;
};
};
OptionTypeDescriptionExample
fileslistOverride global files completelyWeb client APIs only
additionalFileslistExtend global filesAdd Google annotations

Google API Annotations

Add HTTP/REST annotations for web clients

Frontend Boundaries

Prevent frontend access to internal backend services

Browser-Specific APIs

Include browser-only protobuf definitions

Third-Party Protos

Add external protobuf dependencies

proto/example/v1/example.proto
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;
}
import { UserServiceClient } from "./proto/example/v1/example_grpc_web_pb";
import { GetUserRequest, CreateUserRequest, User } from "./proto/example/v1/example_pb";
// Create gRPC-Web client
const client = new UserServiceClient("https://api.example.com");
// Enable CORS credentials
const metadata = { "authorization": "Bearer token" };
// Create user
const 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 promises
const 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 users
const 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");
});
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"paths": {
"@/proto/*": ["./src/proto/*"]
}
},
"include": ["src/**/*"],
"exclude": ["src/proto/**/*_pb.js"]
}
{
"scripts": {
"proto": "bufrnix",
"build": "tsc",
"dev": "tsx watch src/index.ts",
"test": "vitest"
},
"dependencies": {
"@bufbuild/protobuf": "^1.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"tsx": "^4.0.0",
"vitest": "^1.0.0"
}
}
vite.config.ts
export default {
optimizeDeps: {
include: [
"@bufbuild/protobuf",
],
},
};
import { useEffect, useState } from "react";
import { User } from "@/proto/example/v1/example_pb";
// Use your preferred RPC framework (gRPC-Web, Twirp, etc.)
// This example shows the React component structure
export function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch users using your RPC client
fetchUsers()
.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>
);
}
  1. Enable TypeScript: Use target=ts for better type safety
  2. ES Modules: Use .js extensions for proper ES module support
  3. Error Handling: Use proper error types from your RPC framework
  4. Streaming: Use async iterators for efficient streaming
  5. Tree Shaking: ES modules enable better bundle optimization
  6. Smart File Management: Use additionalFiles to include Google API annotations for REST clients
  7. Security Boundaries: Use per-language files to prevent frontend access to internal backend services
Terminal window
cd examples/js-example
nix develop
npm install
npm run build
npm start

For ES modules, ensure proper extensions:

// Correct
import { User } from "./proto/example_pb.js";
// Incorrect
import { User } from "./proto/example_pb";

For browser clients, configure appropriate CORS settings on your server and include authentication headers as needed by your RPC framework (gRPC-Web, Twirp, etc.).

Configure path mapping in tsconfig.json:

{
"compilerOptions": {
"paths": {
"@/proto/*": ["./src/proto/*"]
}
}
}
{
  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";
              
              # Per-language file control (new feature)
              # files = [
              #   "./proto/common/v1/types.proto"
              #   "./proto/api/v1/user_api.proto"
              # ];
              # additionalFiles = [
              #   "./proto/google/api/annotations.proto"  # For Connect-ES REST clients
              #   "./proto/google/api/http.proto"
              # ];

              # Modern JavaScript with ECMAScript modules
              es = {
                enable = true;
              };
            };
          };
        };
      };
    });
}