node.jsapigrpcgrpc-node

Server side gRPC service not getting recognised on client Side


I have been learning about gRPC, and I was trying to implement a gRPC API to create, get and list to-dos. Here is the server side code of the API:

https://github.com/girikgarg8/NodeJS/tree/master/GRPC_Node

And here is the client side code of the API:

https://github.com/girikgarg8/NodeJS/tree/master/Client_NodeGrpc

The server successfully starts on port 50051, but when I try to run the index.js file from the client, it gives me an error:

E:\Backend Development in NodeJS\Client_NodeGrpc\index.js:18
client.listTodos({}, (err, response) => {
       ^

TypeError: client.listTodos is not a function
    at Object.<anonymous> (E:\Backend Development in NodeJS\Client_NodeGrpc\index.js:18:8)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)

Any 
        at Function.Module._load (node:internal/modules/cjs/loader:822:12)
        at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
        at node:internal/main/run_main_module:17:47

Any leads on how to fix this error are appreciated!


Solution

  • You must be precise in your references (e.g. listTodo not listTodos)

    It's a good idea to follow the existing examples closely as you "boot" into a new topic.

    I encourage you to:

    1. Add package something; to your todo.proto
    2. Conventionally (!) protos should be shared between clients and servers

    So...

    1. Move todo.proto to its own directory e.g. protos
    .
    ├── client
    ├── protos
    └── server
    

    In todo.proto:

    syntax="proto3"; //indicates we are using proto version 3
    
    // Use a more applicable name
    package foo;
    
    service TodoService{ //we can define API contract too, in the protocol buffer
        rpc CreateTodo (Todo) returns (Todo) {}
        rpc GetTodo (TodoRequest) returns (Todo) {}
        rpc ListTodo (Empty) returns (TodoList) {}
    }
    
    message Empty {}
    
    message Todo{
        string id=1; //field numbers are used to identify fields in binary encoded data
        string title=2;
        optional string content=3;
    }
    
    message TodoList{
        repeated Todo todos=1; //repeated field indicates that there would be an array of ToDos coming in
    }
    
    message TodoRequest{
        string id=1; // the REST equivalent of this would be /todos/:id (resource based ) vs action based
    }
    

    server/index.js:

    const grpc = require('@grpc/grpc-js');
    const protoLoader = require('@grpc/proto-loader');
    const packageDefinition = protoLoader.loadSync('../protos/todo.proto', {
        keepCase: true,
        longs: String,
        enums: String,
        defaults: true,
        oneofs: true
    });
    
    const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
    
    // The suffix is the package name
    var todoService = protoDescriptor.foo;
    
    const server = new grpc.Server();
    
    const todos = [{
        id: '1',
        title: 'Todo1',
        content: 'Content of todo 1'
    },
    {
        id: '2',
        title: 'Todo2',
        content: 'Content of todo 2'
    }
    ];
    
    // todoService = protoDescriptor.foo
    // TodoService is the name of the gRPC service
    server.addService(todoService.TodoService.service, {
        // below I am defining the RPCs 
        // Your RPC is called `listTodo`
        listTodo: (call, callback) => {
            callback(null, { todos: todos }); // error first callback, as error is null
        },
        createTodo: (call, callback) => {
            let incomingNewTodo = call.request;
            todos.push(incomingNewTodo);
            callback(null, incomingNewTodo);
        },
        getTodo: (call, callback) => {
            let incomingRequest = call.request;
            let todoId = incomingRequest.id;
            const response = todos.filter((todo) => todo.id == todoId);
            if (response.length > 0) {
                callback(null, response);
            } else {
                callback({
                    message: 'Todo not found'
                }, null);
            }
        }
    });
    
    server.bindAsync('localhost:50051', grpc.ServerCredentials.createInsecure(), () => {
        console.log("Server started")
        server.start();
    });
    

    And: client/index.js:

    const grpc = require('@grpc/grpc-js');
    const protoLoader = require('@grpc/proto-loader');
    
    const packageDefinition = protoLoader.loadSync('../protos/todo.proto', {
        keepCase: true,
        longs: String,
        enums: String,
        defaults: true,
        oneofs: true
    });
    
    const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
    const todoService = protoDescriptor.foo;
    
    const client = new todoService.TodoService('localhost:50051', grpc.credentials.createInsecure());
    
    client.listTodo({}, (err, response) => {
        if (err) {
            console.error(err);
            return;
        }
        console.log(response);
    });
    

    And when running:

    {
      todos: [
        {
          id: '1',
          title: 'Todo1',
          content: 'Content of todo 1',
          _content: 'content'
        },
        {
          id: '2',
          title: 'Todo2',
          content: 'Content of todo 2',
          _content: 'content'
        }
      ]
    }