$ open source · GPL-3.0 — need a commercial license? imqueue.com →
← all posts

gRPC vs message-queue RPC for internal Node.js services

gRPC is the default answer for typed RPC — and a great one, especially across languages. For an all-Node.js back-end, routing RPC through a queue trades some of gRPC's strengths for a lot less infrastructure.

When someone says "typed RPC between services," gRPC is the usual answer, and it's a strong one. But it's not the only shape that works, and for an all-Node.js back-end the trade-offs are worth examining. This post compares gRPC with doing RPC over a message queue (the approach @imqueue takes). (gRPC details reflect its documented behavior at the time of writing.)

What gRPC gives you

gRPC is a mature, cross-language RPC system built on HTTP/2 and Protocol Buffers. Its strengths are real:

  • Cross-language by design. Define a .proto once, generate clients and servers in Go, Java, Python, Node, Rust, and more. If your services aren't all one language, this is the headline feature.
  • Efficient binary wire format. Protobuf is compact and fast to encode/decode.
  • Streaming. First-class client, server, and bidirectional streaming over HTTP/2.
  • A schema as a contract. The .proto file is an explicit, versioned contract.

What it costs

Those strengths come with structure you have to operate:

  • A separate schema language. You author and version .proto files, and run a code-generation step in every build. That's a second source of truth alongside your implementation.
  • Addressing is still your problem. gRPC calls a host; something has to tell the caller where the service is and balance across instances — DNS, a service mesh, or client-side load balancing. HTTP/2's long-lived connections also interact awkwardly with naive L4 load balancers.
  • It's request-shaped, not buffered. If a callee is down or slow, the caller feels it directly; you add retries and circuit breakers yourself.

What message-queue RPC changes

Routing RPC through a message queue (the @imqueue model) removes the addressing and balancing problems and drops the schema language, in exchange for narrowing scope:

  • No .proto, no codegen step from a schema. The service is the contract; a typed client is generated from the running service, and the types come from your TypeScript and JSDoc.
  • No discovery or load balancer. A service reads from its named queue; multiple instances compete on that queue and balance themselves. The queue name is the address.
  • Natural back-pressure. If consumers fall behind, the queue absorbs it instead of failing connections.

And what you give up:

  • Language reach. This is a Node.js/TypeScript-first approach. gRPC is the better answer for a polyglot fleet.
  • Streaming semantics. It's request/response over a broker, not HTTP/2 streaming.
  • Raw wire efficiency at extreme scale. Protobuf-over-HTTP/2 is hard to beat on pure encoding cost; a JSON-over-queue approach optimizes for simplicity and developer ergonomics instead.

Quick comparison

@imqueue (queue RPC) gRPC
Contract The service (TS + JSDoc) .proto schema + codegen
Languages Node.js / TypeScript Many (polyglot)
Transport Message queue HTTP/2
Addressing Queue name (no discovery) Host + discovery/mesh
Load balancing Competing consumers Client-side / mesh
Streaming Request/response Full streaming
Wire format JSON (optional gzip) Protobuf (binary)

How to choose

  • Choose gRPC if your services span multiple languages, you need streaming, or you want an explicit schema contract and don't mind running discovery/load-balancing infrastructure.
  • Choose queue-based RPC with @imqueue if your back-end is Node.js/TypeScript, you'd rather not maintain a .proto or a service mesh, and you want typed clients generated straight from your services.

If the second description fits, the Getting Started guide gets you to a working typed call quickly.


Building on @imqueue? The open-source packages live on GitHub and the docs at imqueue.org. Shipping inside a closed-source product? See commercial licensing & support.