Skip to content
Latchkey

How to Run gRPC for Node in CI with Generated Stubs

The pure-JS @grpc/grpc-js avoids native builds, but CI still has to load your .proto definitions and reach a server for integration tests.

For Node gRPC in CI, prefer @grpc/grpc-js (no compiler) plus @grpc/proto-loader for dynamic loading, or generated stubs. The remaining CI work is proto resolution and a reachable test server.

Why it fails in CI

  • A relative .proto path resolves differently in CI → ENOENT loading the proto.
  • Integration tests start before the gRPC server is listening → UNAVAILABLE.
  • Generated stubs are stale relative to the committed .proto files.

Install and run it reliably

Install the pure-JS packages, load protos by absolute path, and gate integration tests on the server being up.

Terminal
npm install @grpc/grpc-js @grpc/proto-loader

# load protos by absolute path so CI cwd does not matter
node -e "const path=require('path'); require('@grpc/proto-loader').load(path.join(process.cwd(),'proto/service.proto'))"

# regenerate stubs in CI if you check them in
npm run proto:gen && git diff --exit-code src/generated

Cache & speed

Standard ~/.npm caching is enough since there is no native build. Start the gRPC server as a background step and wait for the port before tests.

.github/workflows/ci.yml
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}

Common errors

  • ENOENT ... service.proto → use an absolute proto path in CI.
  • 14 UNAVAILABLE: No connection established → the server is not up yet; wait on the port.
  • Drift between stubs and protos → regenerate and git diff --exit-code.

Key takeaways

  • Prefer @grpc/grpc-js + proto-loader to avoid native builds.
  • Load .proto files by absolute path so CI cwd does not break them.
  • Wait on the server port before integration tests.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →