Get Started
Before this, I hope you have some understanding of gRPC and protobuf, which is the best, of course, you can also get familiar with it here. Here we will use a simple example for a quick start, divided into four steps, which are project initialization, loading proto, implementing the server, and implementing the client.
Project Initialization
Create Project
Create a demo project and enter:
mkdir demo && cd demoInitialize and install dependencies
npm init -y
npm i grpcityNote: We need to add "type": "module" in package.json to activate ESM.
Project Directory
We need to create a series of directories and files. The final project directory and file structure are as follows. The highlighted parts need to be created by us in advance:
.
βββ client.js
βββ loader.js
βββ package-lock.json
βββ package.json
βββ proto
β βββ helloworld
β βββ model
β β βββ message.proto
β βββ service.proto
βββ server.jsLoad Proto
Define Proto
We define two services, Greeter and Hellor, and split message into model.
Enter the following content for service.proto:
syntax = "proto3";
package helloworld;
import "helloworld/model/message.proto";
service Greeter {
rpc SayGreet(HelloRequest) returns (HelloReply) {}
}
service Hellor {
rpc SayHello(HelloRequest) returns (HelloReply) {}
}Enter the following content for message.proto:
syntax = "proto3";
package helloworld.model;
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
int32 count = 2;
}Implement Loader
Enter the following content for ./loader.js:
import { ProtoLoader } from 'grpcity'
import path from 'node:path'
// __dirname for esm
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default new ProtoLoader({
location: path.join(__dirname, './proto'),
files: [
'helloworld/service.proto'
]
})In ProtoLoader, location and files respectively represent the proto directory and service proto file.
So far, we have completed the loading of proto files and got loader.js. This way we can import this file anywhere and support us to develop and implement the client or server.
Implement Server
All the steps here will be done in the ./server.js file.
Import Loader
import loader from "./loader.js"Implement Greeter
Create Greeter and implement the sayGreet method:
class Greeter {
constructor() {
this.count = 0
}
async sayGreet(call) {
const { name } = call.request
this.count++
return {
message: `hello ${name || "world"} by Greeter`,
count: this.count
}
}
}Implement Hellor
Continue to implement Hellor in the same way
class Hellor {
async sayHello(call) {
const { name } = call.request
return { message: `hello ${name || "world"} by Hellor` }
}
}Bind and Start
const start = async (addr) => {
// loader initialization
await loader.init()
// server initialization and get instance
const server = await loader.initServer()
// bind class methods with service
server.add('helloworld.Greeter', new Greeter())
server.add('helloworld.Hellor', new Hellor())
// listen
await server.listen(addr)
console.log('helloworld server is started: ', addr)
}
// start
start('127.0.0.1:9098')Implement Client
Import Loader
import loader from "./loader.js"Get Client
Create start method, complete getting client.
const start = async (addr) => {
await loader.init()
const clients = await loader.initClients({
services: {
'helloworld.Greeter': addr,
'helloworld.Hellor': addr
}
})
}Call and Start
We then supplement the logic of the client calling the server in the start method and print the call result.
const start = async (addr) => {
// ...
// loader init
// clients init
// ....
// greeter client
const greeterClient = clients.get('helloworld.Greeter')
const greeterResult = await greeterClient.sayGreet({ name: 'grpcity' })
console.log('greeterClient.sayGreet', greeterResult.response)
// hellor client
const hellorClient = clients.get('helloworld.Hellor')
const hellorResult = await hellorClient.sayHello({ name: 'grpcity' })
console.log('hellorClient.sayHello', hellorResult.response)
}
// execute
start('127.0.0.1:9098')The result received after the client initiates the call includes four values, which are status, peer, metadata, and response. Here, we only need to print out response.
Debug
We need two terminal windows, one for running the server and the other for running the client.
Start Server
node ./server.js
helloworld server is started: 127.0.0.1:9098After starting, our client can connect to the corresponding address.
Start Client
Start the client, establish a connection with the server, and execute the call. Here, we will execute the client twice.
The result of the first execution is as follows:
node ./client.js
greeterClient.sayGreet { message: 'hello grpcity by Greeter', count: 1 }
hellorClient.sayHello { message: 'hello grpcity by Hellor' }The result of the second execution is as follows:
node ./client.js
greeterClient.sayGreet { message: 'hello grpcity by Greeter', count: 2 }
hellorClient.sayHello { message: 'hello grpcity by Hellor' }We can observe that both executions return as expected. In the sayGreet function, the count is incremented to 2 after the second run, aligning with our expectations and confirming the successful completion of this client-server interaction loop.
And that wraps up our quick start tutorial, designed to be straightforward and user-friendly. For a deeper understanding, feel free to explore the User Guide.
The code for this tutorial is available on GitHub.