Skip to Content
🎉 gRPCity 3.0 is released. Read more →

Loader

ProtoLoader is the entry point for everything else in gRPCity. You construct it with a description of where your .proto files live, then call init() once; the resulting instance feeds both the client and the server.

import { ProtoLoader } from 'grpcity' const loader = new ProtoLoader({ location, files })

The two arguments serve different roles:

  • location is the root directory the loader searches, including transitive imports such as model.proto or empty.proto.
  • files lists the specific service.proto files to load. The loader uses these to decide which services and types to expose.

Splitting the two means:

  • the loaded services are explicit and easy to audit, and
  • you can lay out your proto directory however you like without affecting load behaviour.

Resolving the directory

In ESM, __dirname is not a global. Derive it from import.meta.url:

import { ProtoLoader } from 'grpcity' import path from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url))

On Node.js >= 20.11 you can use import.meta.dirname instead. In CommonJS, __dirname is still the built-in global.

A single directory

The common case: one proto directory, several service files.

export default new ProtoLoader({ location: path.join(__dirname, './proto'), files: ['path/to/service-a.proto', 'path/to/service-b.proto'] })

Multiple directories

When you need to load protos from more than one source — for example, two business domains living side by side — pass an array.

export default new ProtoLoader([ { location: path.join(__dirname, './proto1'), files: ['path/to/service-a.proto'] }, { location: path.join(__dirname, './proto2'), files: ['path/to/service-b.proto'] } ])

Initializing

init() actually reads and parses the proto files. It’s also where you pass environment options, so it sits naturally at the top of your service bootstrap.

await loader.init(options)

options accepts the following fields:

  • isDev — (optional, boolean) toggles dev mode. On its own it does nothing visible; it gates packagePrefix.
  • packagePrefix — (optional, string) only honoured when isDev is true. Rebinds every loaded service and type under <prefix>.<originalName>, and the on-wire gRPC method paths become /<prefix>.<package>.<Service>/.... Handy for sandboxing tenants or environments.
  • loadOptions — (optional, object) passed straight through to @grpc/proto-loader. Your object overrides the defaults field-by-field. Defaults: keepCase: true, longs: String, enums: String, defaults: false, oneofs: true.

See Config for a full walk-through of loadOptions.

init() is idempotent and concurrent-safe: calling it multiple times — even from multiple entry points before the first call resolves — shares a single in-flight promise. initServer, initClients, and initReflection call it internally if you haven’t, so the only reason to await loader.init(options) explicitly is when you want to pass options or call service() / type() directly.

Picking a prefix per environment

When you use packagePrefix, both the server and the client must use the same value — they need to agree on the on-wire path.

Test environment:

await loader.init({ isDev: true, packagePrefix: 'test' })

Stage environment:

await loader.init({ isDev: true, packagePrefix: 'stage' })

Production: leave both options off.

await loader.init()
Last updated on