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-connect-esConnect-ES RPC*_connect.js
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
];
};
# 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/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 { 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 transport
const 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 client
const 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();
{
"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": {
"@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"
}
}
vite.config.ts
export default {
optimizeDeps: {
include: [
"@connectrpc/connect",
"@connectrpc/connect-web",
"@bufbuild/protobuf",
],
},
};
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>
);
}
  1. Use Connect-ES: Preferred for new projects - type-safe, modern API
  2. Enable TypeScript: Use target=ts for better type safety
  3. ES Modules: Use .js extensions for proper ES module support
  4. Error Handling: Use proper error types from Connect/gRPC
  5. Streaming: Use async iterators for efficient streaming
  6. Tree Shaking: ES modules enable better bundle optimization
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 server CORS:

const transport = createConnectTransport({
baseUrl: "https://api.example.com",
credentials: "include", // For cookies
// OR
headers: {
Authorization: "Bearer token",
},
});

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";
              # Modern JavaScript with ECMAScript modules
              es = {
                enable = true;
              };
            };
          };
        };
      };
    });
}