An Express application can serve gRPC, but it’s not a direct, one-to-one mapping; it’s more like Express is acting as a gateway or proxy.
Let’s see it in action. Imagine you have a simple gRPC service defined in a .proto file:
syntax = "proto3";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
You’d compile this into JavaScript using grpc_tools_node_protoc.
Now, you’d typically have a separate gRPC server process. But to serve it from Express, you’d spin up your gRPC server within the same Node.js process as your Express app.
Here’s a simplified Express setup:
const express = require('express');
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const app = express();
const port = 3000; // Express port
const grpcPort = 50051; // gRPC port
// Load the protobuf definition
const packageDefinition = protoLoader.loadSync('greeter.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const greeterProto = protoDescriptor.Greeter;
// Implement the gRPC service
const server = new grpc.Server();
server.addService(greeterProto.service, {
sayHello: (call, callback) => {
callback(null, { message: `Hello ${call.request.name}` });
},
});
// Start the gRPC server in the background
server.bindAsync(`0.0.0.0:${grpcPort}`, grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log(`gRPC server started on port ${grpcPort}`);
});
// Express route to proxy gRPC requests
app.post('/greet', express.json(), (req, res) => {
const client = new greeterProto.Greeter(`localhost:${grpcPort}`, grpc.credentials.createInsecure());
const name = req.body.name;
client.sayHello({ name: name }, (error, response) => {
if (error) {
console.error('gRPC client error:', error);
res.status(500).send({ error: 'gRPC call failed' });
} else {
res.send({ message: response.message });
}
});
});
app.listen(port, () => {
console.log(`Express app listening at http://localhost:${port}`);
});
With this setup, Express isn’t directly handling gRPC. Instead, it’s acting as an HTTP API endpoint that, when invoked, makes a gRPC call to the actual gRPC server running in the same process. This pattern is useful for exposing a gRPC service via a more common RESTful API or for integrating gRPC services into an existing Express monolith.
The core problem this solves is bridging the gap between HTTP-based clients (browsers, most web services) and a gRPC backend. You can use Express as the front door. Clients send JSON over HTTP to your Express app, and Express translates that into gRPC calls to the internal gRPC server. The response from gRPC is then translated back into JSON for the HTTP client.
Internally, the grpc.Server instance is created and starts listening on its own port (50051 in this example). The Express application, running on 3000, has a route (/greet) that accepts POST requests. When a request hits this route, Express parses the JSON body, creates a gRPC client instance pointing to the internal gRPC server address, and then invokes the sayHello gRPC method. The result is sent back as a JSON response.
The key levers you control are:
- Proto Definition: The
.protofile dictates the entire contract of your gRPC service. - gRPC Server Implementation: How you implement the methods defined in the
.protofile. - Express Routing: The HTTP endpoints you expose and how they map to gRPC calls.
- gRPC Client Configuration: How your Express app connects to the gRPC server (port, credentials, etc.).
Most people don’t realize that the grpc.Server and grpc.Client classes are designed to be flexible. You can instantiate them anywhere in your application’s lifecycle. In this case, we’re instantiating the grpc.Server early to be ready to receive calls when the Express proxy route is hit. The grpc.ServerCredentials.createInsecure() is a simplification; for production, you’d want to use createSsl().
The next logical step is to consider how to handle streaming gRPC calls from an Express proxy.