Server Reflection
Server Reflection allows clients to discover services, methods, and message types at runtime without access to .proto files. Connectum implements the gRPC Server Reflection Protocol (v1 and v1alpha) through the @connectum/reflection package.
Why Use Reflection?
- grpcurl: List and call services without providing
.protofiles - Postman / Insomnia / Warthog: Auto-discover gRPC services for manual testing
- buf curl: Test ConnectRPC services with automatic schema resolution
- Service registries: Dynamic service discovery in microservice architectures
- Development: Explore APIs interactively during development
Production consideration
Server Reflection exposes your service schema to any client that can connect. In production environments, consider disabling it or restricting access.
Installation
pnpm add @connectum/reflectionPeer dependency: @connectum/core.
Quick Setup
import { createServer } from '@connectum/core';
import { Reflection } from '@connectum/reflection';
import routes from '#gen/routes.js';
const server = createServer({
services: [routes],
port: 5000,
protocols: [Reflection()],
});
await server.start();That is all you need. The Reflection() factory creates a ProtocolRegistration that registers the grpc.reflection.v1.ServerReflection service on your server.
How It Works
When you pass Reflection() to the protocols array, Connectum:
- Collects all registered service file descriptors from your services
- Builds a
FileDescriptorSetfrom those descriptors and their dependencies - Registers the
grpc.reflection.v1.ServerReflectionservice on the ConnectRouter - Clients can then query the reflection service to discover available services
The reflection service is registered after your application services, so it has access to all registered service descriptors.
Using grpcurl with Reflection
grpcurl is the most common tool for interacting with reflection-enabled gRPC services.
List All Services
grpcurl -plaintext localhost:5000 listOutput:
greeter.v1.GreeterService
grpc.health.v1.Health
grpc.reflection.v1.ServerReflectionDescribe a Service
grpcurl -plaintext localhost:5000 describe greeter.v1.GreeterServiceOutput:
greeter.v1.GreeterService is a service:
service GreeterService {
rpc SayHello ( .greeter.v1.SayHelloRequest ) returns ( .greeter.v1.SayHelloResponse );
}Describe a Message Type
grpcurl -plaintext localhost:5000 describe greeter.v1.SayHelloRequestOutput:
greeter.v1.SayHelloRequest is a message:
message SayHelloRequest {
string name = 1;
}Call a Method
With reflection enabled, grpcurl does not need -proto or -protoset flags:
grpcurl -plaintext \
-d '{"name": "Alice"}' \
localhost:5000 \
greeter.v1.GreeterService/SayHelloWith TLS
# Self-signed (development)
grpcurl -insecure localhost:5000 list
# With CA certificate
grpcurl -cacert keys/server.crt localhost:5000 listUsing buf curl with Reflection
buf curl supports ConnectRPC protocol and can use reflection:
# List services
buf curl --protocol connect --http2-prior-knowledge \
http://localhost:5000 --list-services
# Call a method
buf curl --protocol connect --http2-prior-knowledge \
-d '{"name": "Alice"}' \
http://localhost:5000/greeter.v1.GreeterService/SayHelloUsing Postman / Insomnia / Warthog
Postman, Insomnia, and Warthog support gRPC Server Reflection:
- Create a new gRPC request
- Enter the server URL:
localhost:5000 - Click "Use Server Reflection" (or similar)
- The tool discovers and lists all available services and methods
- Select a method, fill in the request payload, and send
Conditional Reflection
Enable reflection only in non-production environments:
import { createServer } from '@connectum/core';
import { Healthcheck, healthcheckManager, ServingStatus } from '@connectum/healthcheck';
import { Reflection } from '@connectum/reflection';
const protocols = [Healthcheck({ httpEnabled: true })];
// Only enable reflection in development
if (process.env.NODE_ENV !== 'production') {
protocols.push(Reflection());
}
const server = createServer({
services: [routes],
port: 5000,
protocols,
});
server.on('ready', () => {
healthcheckManager.update(ServingStatus.SERVING);
});
await server.start();Adding Reflection at Runtime
You can add reflection before starting the server using addProtocol():
const server = createServer({
services: [routes],
port: 5000,
});
// Conditionally add reflection
if (config.enableReflection) {
server.addProtocol(Reflection());
}
await server.start();WARNING
addProtocol() can only be called before server.start(). Attempting to add protocols after the server has started throws an error.
Complete Example
import { createServer } from '@connectum/core';
import { Healthcheck, healthcheckManager, ServingStatus } from '@connectum/healthcheck';
import { Reflection } from '@connectum/reflection';
import { createDefaultInterceptors } from '@connectum/interceptors';
import { greeterServiceRoutes } from './services/greeterService.ts';
import { orderServiceRoutes } from './services/orderService.ts';
const server = createServer({
services: [greeterServiceRoutes, orderServiceRoutes],
port: 5000,
protocols: [
Healthcheck({ httpEnabled: true }),
Reflection(),
],
interceptors: createDefaultInterceptors(),
shutdown: { autoShutdown: true },
});
server.on('ready', () => {
const port = server.address?.port;
console.log(`Server ready on port ${port}`);
console.log(` grpcurl -plaintext localhost:${port} list`);
healthcheckManager.update(ServingStatus.SERVING);
});
await server.start();After starting, verify everything is discoverable:
# List all services
grpcurl -plaintext localhost:5000 list
# greeter.v1.GreeterService
# order.v1.OrderService
# grpc.health.v1.Health
# grpc.reflection.v1.ServerReflection
# Describe the order service
grpcurl -plaintext localhost:5000 describe order.v1.OrderServicecollectFileProtos Utility
The collectFileProtos utility function is also exported for advanced use cases. It collects file descriptor protos with their dependency tree:
import { collectFileProtos } from '@connectum/reflection';This is used internally by the Reflection() factory to build the FileDescriptorSet.
Protocol Registration Details
Under the hood, Reflection() returns a ProtocolRegistration object:
{
name: 'reflection',
register(router, context) {
// context.registry contains all registered service DescFile[]
// Builds FileDescriptorSet and registers reflection service
},
}The context.registry is populated by @connectum/core during server startup, containing file descriptors from all registered services.
Related
- Protocols Overview -- back to overview
- Custom Protocols -- create your own protocol plugins
- Health Checks -- health monitoring
- @connectum/reflection -- Package Guide
- @connectum/reflection API -- Full API Reference
