Generating code
We mentioned earlier that the ELIZA service defines a Protocol Buffer schema. So what is that schema? It is really just a simple file that describes the service, its methods, and their argument and return types:
syntax = "proto3";
service ElizaService {
rpc Say(SayRequest) returns (SayResponse) {}
}
message SayRequest {
string sentence = 1;
}
message SayResponse {
string sentence = 1;
}
You can see the full version including comments and some additional RPCs
on the Buf Schema Registry (BSR).
The rpc
keyword stands for Remote Procedure Call — a method you can invoke
remotely. The schema is the contract between server and client, and it
precisely defines how data is exchanged down to the very details of
serialization.
The schema comes to life by generating code. For the server, an interface is generated, and the developer can focus on filling the methods with business logic. For the client, there really isn't anything to do — the developer can just call the client methods, rely on the generated types for compile-time type-safety and serialization, and focus on the application logic.
Generated SDKs
In the tutorial, we have been using generated SDKs
with an npm install
command. When the package was requested on the BSR NPM registry, it ran
the schema through a code generator, and served the generated files as a
package with all required dependencies.
If you want to use a Connect or gRPC service whose schema is published on the
BSR, you can simply use npm
to install the package, and hit the service with
a Connect client.
See our documentation on generated SDKs for details.
Local generation
We're going to generate our code using Buf, a modern replacement for Google's protobuf compiler, and two compiler plugins:
- @connectrpc/protoc-gen-connect-es — generates services from your Protocol Buffer schema
- @bufbuild/protoc-gen-es — generates base types, like request and response messages
The code we will generate has three runtime dependencies:
- @connectrpc/connect — provides clients, interceptors, errors, and other primitives for Connect
- @connectrpc/connect-web — provides the Connect and gRPC-web protocols for web browsers
- @bufbuild/protobuf — provides serialization and more for the base types
First, let's install buf
, the plugins and runtime dependencies:
$ npm install --save-dev @bufbuild/buf @connectrpc/protoc-gen-connect-es@"^1.0.0" @bufbuild/protoc-gen-es@"^1.0.0"
$ npm install @connectrpc/connect@"^1.0.0" @connectrpc/connect-web@"^1.0.0" @bufbuild/protobuf@"^1.0.0"
Next, tell Buf to use the two plugins with a new configuration file:
# buf.gen.yaml defines a local generation template.
# For details, see https://buf.build/docs/configuration/v2/buf-gen-yaml
version: v2
plugins:
# This will invoke protoc-gen-es and write output to src/gen
- local: protoc-gen-es
out: src/gen
opt: target=ts
# This will invoke protoc-gen-connect-es
- local: protoc-gen-connect-es
out: src/gen
# Add more plugin options here
opt: target=ts
If desired, you can also skip local plugin installation and use remote plugins. See the connect-es example for a buf.gen.yaml which uses remote plugins.
Finally, tell Buf to generate code for the ELIZA schema:
$ npx buf generate buf.build/connectrpc/eliza
If you prefer, you can use protoc
instead of Buf — the plugins behave like
any other plugin.
Output
Let's take a peek at what was generated. There are two new files:
src/gen/connectrpc/eliza/v1/eliza_connect.ts
src/gen/connectrpc/eliza/v1/eliza_pb.ts
The first file was generated by protoc-gen-connect-es
and contains the
service:
import { SayRequest, SayResponse } from "./eliza_pb.js";
import { MethodKind } from "@bufbuild/protobuf";
export const ElizaService = {
typeName: "connectrpc.eliza.v1.ElizaService",
methods: {
say: {
name: "Say",
I: SayRequest,
O: SayResponse,
kind: MethodKind.Unary,
},
}
} as const;
The full file includes comments and additional RPCs, but the const
above really is all Connect needs to provide clients.
The second file was generated by protoc-gen-es
, and contains the request and
response classes. You can see them being imported for the service definition.
To learn more about protoc-gen-es
, head over to the documentation for the
Protobuf-ES project.
If your bundler does not handle the .js
extension in the import from "./eliza_pb.js"
correctly, you can configure it following our examples here,
or you can add the plugin option import_extension=none
to remove the extension.
You can find the documentation for all available plugin options on npmjs.com.
Using the local files
To use the locally generated files in the tutorial, update the import path:
- import { ElizaService } from "@buf/connectrpc_eliza.connectrpc_es/connectrpc/eliza/v1/eliza_connect";
+ import { ElizaService } from "./gen/connectrpc/eliza/v1/eliza_connect";