Posted in

How to build a gRPC service with Express?

If you’ve spent any time building APIs with Express, you’ve already fallen in love with its flexibility, lightweight structure, and massive ecosystem of tools that turn basic server setups into production-ready workhorses. As someone who’s spent the last five years helping businesses scale their Express services, I’ve watched teams use it for everything from small internal tools to global e-commerce backends, all while hitting roadblocks when they need to move beyond simple REST endpoints. That’s where gRPC comes in. It’s not a replacement for Express, but a complementary layer that solves some of the biggest pain points of high-traffic applications—fast, efficient communication between services, and the ability to handle millions of requests without slowing down. Today, I’m walking you through exactly how to build a gRPC service with Express, using tools that play nicely with your existing Express stack, no full rewrite required. Express

Let’s start with the basics, because I’ve seen teams skip this and end up confused when their gRPC calls won’t connect. gRPC is a high-performance remote procedure call (RPC) framework built by Google, which uses HTTP/2 for transport and Protocol Buffers (Protobuf) for serializing data, instead of the plain JSON Express often uses for REST. The biggest upside here is speed: HTTP/2 keeps connections open and multiplexes multiple requests over a single connection, so you don’t have to waste time opening new connections for every call, and Protobuf is way smaller and faster to parse than JSON, cutting down on payload size. That’s a huge win for microservices or apps where multiple Express services need to talk to each other quickly.

Here’s the best part: you don’t have to abandon your Express setup to use gRPC. You can keep your existing Express server for handling client-facing REST requests (like from a web app or mobile frontend) and add a gRPC server right alongside it for internal service-to-service communication. This hybrid approach is perfect for teams that already have a working Express codebase and don’t want to rip it out to adopt a whole new stack. For example, if you run an e-commerce platform built on Express, you might use your Express server to handle product searches for customers, and add a gRPC endpoint that your internal inventory service can call to update stock levels in real time—no extra latency, and it keeps your client-facing code simple and familiar.

Before we dive into code, let’s talk about the tools we’ll use, because using the right packages saves you hours of debugging. The main libraries here are @grpc/grpc-js, which is the modern, maintained gRPC implementation for Node.js, and @grpc/proto-loader, which lets you load your Protobuf definitions without a lot of boilerplate. We’re not using the old grpc package, which is deprecated, and we’re also keeping things Express-friendly, so we won’t have to restructure your Express setup at all.

First, set up your project. If you already have an existing Express project, skip the initial setup—just run npm install to add the new packages. If you’re starting fresh, create a new folder, run npm init -y, then install the dependencies: express, @grpc/grpc-js, @grpc/proto-loader, and @grpc/proto-loader. Wait, no, that’s redundant—just install express, @grpc/grpc-js, and @grpc/proto-loader. That’s all you need. Let me repeat that: no fancy, overcomplicated tools, just the official, well-maintained packages that play nice with Express.

Now, the core of any gRPC service is the Protobuf file. This is where you define your service, the methods it offers, and the data types it uses. Think of this as a contract between your service and any client that wants to use it—there’s no ambiguity about what data is sent or how the calls work, which is way more reliable than REST, where you often have to check API documentation to know if a field is optional or required. For our example, let’s build a simple user service that can get a user by ID and create a new user. Create a file called user.proto in your project root. Here’s what that looks like:

syntax = "proto3";

package user;

service UserService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
}

message GetUserRequest {
string user_id = 1;
}

message GetUserResponse {
string user_id = 1;
string name = 2;
string email = 3;
}

message CreateUserRequest {
string name = 1;
string email = 2;
}

message CreateUserResponse {
string user_id = 1;
string name = 2;
string email = 3;
}

Let me explain this so it makes sense. The syntax is proto3, which is the latest standard. The package is user, so we don’t have naming conflicts with other services. The UserService has two methods: GetUser, which takes a GetUserRequest (that just has a user ID string) and returns a GetUserResponse with the user’s details. CreateUser takes a name and email and returns the full user object with a generated ID. This is a simple, straightforward contract that any gRPC client can understand, no guessing required.

Next, we need to write the code that implements this gRPC service, and ties it to our Express server. Let’s create a file called server.js. First, require all the packages we installed: express, grpc from @grpc/grpc-js, and protoLoader from @grpc/proto-loader. Then, load the Protobuf file. The protoLoader.load method takes the path to our .proto file, and an options object that sets keepCase to true (so our field names don’t get converted to camelCase automatically, which keeps things consistent) and longs: String, which makes sure long numbers don’t get parsed as big integers we have to handle weirdly.

Once we load the Protobuf, we get a package definition, then load that into grpc.loadPackageDefinition to get the actual service types. Then, we create an implementation for our UserService. This is where we write the logic for our gRPC methods, just like writing Express route handlers. For GetUser, we’ll simulate a database lookup—right now, we’ll use an in-memory object for testing, but you can easily replace this with a real database call later. If the user ID exists, we return the user data; if not, we throw an error, which gRPC will send back to the client. For CreateUser, we’ll generate a random user ID, save the new user to our in-memory store, and return the created user details.

Now, here’s the key part: integrating this with Express. We can run the gRPC server and the Express server on the same Node.js process, using different ports. That way, we don’t have to manage two separate processes, which simplifies deployment. We’ll set the gRPC server to run on port 50051, and the Express server to run on port 3000. For the Express part, we’ll add a simple route as an example—maybe a client-facing route that returns a welcome message, just to show that our Express server is still working as normal.

Wait, let’s make sure the code for the implementation makes sense. The UserService implementation is an object with GetUser and CreateUser methods. Each of these methods takes a call object (which has the request data from the client) and a callback function, which we call when we’re done processing the request. For example, in GetUser, we get the user_id from call.request, check if it exists in our user database, and if it does, call the callback with null (meaning no error) and the user data. If not, we call the callback with an error, which gRPC will format properly. That’s just like how Express route handlers take req and res objects—familiar pattern, right?

Now, let’s test this. Save all your files, run node server.js, and you’ll see that both servers start: the Express server on port 3000, and the gRPC server on port 50051. To test the gRPC service, you can use a tool like grpcurl, which lets you call gRPC endpoints from the command line, without writing a whole client. Run grpcurl -d ‘{"user_id": "123"}’ localhost:50051 user.UserService/GetUser, and you should get back the test user data we created. You can also test the CreateUser method with grpcurl -d ‘{"name": "Jane Doe", "email": "jane@example.com"}’ localhost:50051 user.UserService/CreateUser, and it will return the new user with a generated ID. The Express server is still running on port 3000—if you go to localhost:3000 in your browser, you’ll see the welcome message we set up, just like a normal Express app.

But wait, what if you want to use this gRPC service with another Express microservice? That’s where this setup really shines. Let’s say you have a second Express service that needs to call the user gRPC service to get user details for an order endpoint. You can add the same gRPC client setup to that service, and call the GetUser method just like you’d call an Express API endpoint, but with way less latency. The client code looks almost identical to the server code: you load the Protobuf, create a client, and call the method with a callback. This consistency means your team doesn’t have to learn a whole new way of making requests, just a new library.

Now, let’s talk about common pitfalls I’ve seen teams run into, so you can avoid them. First, never put your gRPC server on the same port as your Express server—you’ll get conflicts. Using different ports in the same process is safe, but if you scale to multiple processes, you can run gRPC on port 50051 for internal traffic and expose Express on 3000 for external traffic. Second, make sure your Protobuf file is correctly formatted—even a missing semicolon or a typo in a method name will cause the loader to fail, so double-check that syntax. Third, don’t overcomplicate things with unnecessary features like streaming until you’re comfortable with unary RPCs, which are the simplest and most common type. Most internal service calls use unary RPCs, so start there.

Another big question I get: when should you use this hybrid setup vs. a fully gRPC-only stack? If you’re building a new application from scratch, you could technically use Express just for the gRPC server, but if you have existing Express services, this is the most seamless way to add gRPC functionality without rewriting everything. As an Express specialist, I always advise businesses to leverage their existing investment in Express first—your team knows the framework, the tools, and the codebase, so adding gRPC as a complementary layer reduces risk and speeds up development. For example, a client of ours had a 3-year-old Express e-commerce backend that was handling REST requests fine, but their internal inventory service was taking 200ms to respond with REST, leading to checkout delays. We added a gRPC service alongside their existing Express server, cut the inventory service response time to 20ms, and kept their client-facing Express routes exactly the same—no user-facing changes, just better performance.

If you’re worried about deployment, running both servers in the same process makes deployment simple. You can deploy this as a single Node.js service, just like any other Express app, whether you’re using Docker, Kubernetes, or a traditional VPS. You don’t have to manage two separate deployments, which reduces operational overhead. If you later need to scale the gRPC service independently (say, if you have a lot of internal service calls but less client traffic), you can split them into two separate services, but that’s a problem for later—start with the simple setup and adjust as needed.

Now, let’s wrap this up with next steps. If you’ve followed along, you have a working Express app with a gRPC service running alongside it. The next steps would be to replace the in-memory user store with a real database—MongoDB, PostgreSQL, whatever you already use with Express. You can also add authentication to your gRPC service, just like you would with Express routes, using middleware to verify tokens or API keys. For example, you can add an interceptor to your gRPC server that checks for an authorization header, just like Express middleware, to restrict access to internal services only. If you need streaming (like real-time updates for inventory), you can add bidirectional streaming methods to your Protobuf file, and implement them in your server code—still following the same pattern you’re used to with Express event handling.

Express UPS At the end of the day, building a gRPC service with Express isn’t about throwing out everything you know—its about extending your existing Express stack to handle the high-performance communication your applications need. As an Express provider, our goal is to help you get the most out of your existing tools, not force you to start over. If you’re ready to implement this in your own project, or need help troubleshooting gRPC-Express integration, our team is here to support you. Contact our procurement team to discuss your specific needs, whether that’s building a new service, scaling an existing one, or optimizing performance for your microservices architecture.

References

  1. “gRPC Official Documentation.” Google LLC.
  2. “Express.js Official Guide.” Express Foundation.
  3. “Protocol Buffers Language Guide (proto3).” Google LLC.
  4. @grpc/grpc-js Documentation. grpc.io.

Foshan Shijie International Logistics Co., Ltd.
With abundant experience, we are one of the most professional express service suppliers in China. We are committed to offering reliable express service and logistics solutions with low price. If you have any enquiry about quote, please feel free to email us.
Address: Room 701A, Office Building, Jinbo Commercial Center, No. 88, Guiye Road, Guicheng Street, Nanhai District, Foshan City, Guangdong Province
E-mail: alicesmith.tradingbusiness@gmail.com
WebSite: https://www.fsshijielogistics.com/