tRPC
@heronjs/trpc exposes tRPC v11 procedures through the shared HTTP listener.
Register procedure controllers in application modules; the extension mounts their
routers before the listener accepts requests.
Register procedures
import 'reflect-metadata';
import {Module} from '@heronjs/common';
import {HeronJS} from '@heronjs/core';
import {Procedure, Query, Mutation} from '@heronjs/trpc';
import {z} from 'zod';
@Procedure()
class GreetingProcedures {
@Query({input: z.object({name: z.string()})})
hello(input: {name: string}) {
return {greeting: `Hello ${input.name}`};
}
@Mutation()
save(input: unknown) {
return input;
}
}
@Module({rpcs: [GreetingProcedures]})
class App {}
const app = await HeronJS.create({module: App}); // HTTP is the default.
app.add({trpc: {prefix: '/trpc', hooks: []}}); // Optional configuration.
await app.listen({port: 3000});
// await app.close();The rpcs list also works through imported modules. Declare controllers in module
metadata; importing decorators does not register global runtime state. Core awaits registration and mounting before
opening the shared HTTP listener. No separate tRPC port or stdio transport is used.
The @heronjs/express extension provides the native Fastify host. Standalone
adapters require the same Fastify plugin-registration capability.
Inputs and execution
The class decorator and all method decorators preserve their no-argument forms.
Query({ input }), Mutation({ input }) and Subscription({ input }) optionally
accept a parser such as a Zod schema. Without one, input passes through unchanged;
validate it in the handler. Methods receive (input, context) with their DI instance
binding preserved. Context contains Fastify req and res, optional authContext, and an
abort signal for HTTP requests.
Procedures are exposed by method name, which must be unique across controllers. Conflicting query/mutation/subscription decorators and duplicate procedure names fail startup. Register all procedures before listening. Repeated extension registration and startup are idempotent, with state local to the extension.
Queries/mutations support values, Promises and RxJS Observables (first emission).
Subscription() supports async iterables and RxJS Observables over tRPC HTTP/SSE. Observe the context
signal during long-running work. Disposal aborts owned HTTP requests/subscriptions
and unsubscribes pending Observable queries while leaving the shared server under
core ownership. Native WebSocket transport is not installed by this adapter.
Authentication and configuration
Class and method Guard rules are both enforced. The DI-resolved gatekeeper and
optional HTTP auth resolver populate request identity. Missing authentication
returns UNAUTHORIZED; mismatched string, number, boolean, array and async
predicate requirements return FORBIDDEN. Promised requirements are awaited.
Use Fastify plugins and hooks for protocol-specific configuration.
Express middlewares options are rejected; cookie/session parsing belongs to
the HTTP extension. Unexpected HTTP errors are sanitized; explicit
TRPCError codes and client-facing messages are retained.
Connect a client
import {createTRPCUntypedClient, httpBatchLink} from '@trpc/client';
const client = createTRPCUntypedClient({
links: [httpBatchLink({url: 'http://localhost:3000/trpc'})],
});
console.log(await client.query('hello', {name: 'Heron'}));Decorated controllers are assembled dynamically; they do not automatically produce
an inferred application router type for createTRPCClient<AppRouter>. The official
untyped client supports this dynamic router. Use the official httpSubscriptionLink
for SSE; Node clients need an EventSource implementation.
Direct adapter usage remains available through TRPCAdapter (and its original
tRPCAdapter alias), TRPCServer, authorize, and Authorization2Way.
protocols.trpc is preferred; legacy subServer.trpc remains accepted. A standalone
adapter has close() for disposal; the server’s listen() only mounts middleware.
References: Fastify adapter , input validation , subscriptions .
See the Fastify migration guide.
Multiple procedure endpoints
@Procedure('/users') and @Procedure('/admin') create independent routers at
/trpc/users and /trpc/admin. Methods can have the same name in different
routers. Connect an SDK client to each URL and invoke its ordinary method names.
A configured trpc.prefix, such as /api/rpc, replaces the /trpc base.
@Procedure() retains the default router at /trpc. Multiple controllers without
a path contribute to that router and must use unique method names. Duplicate
names are validated within their endpoint. Guards, DI, input validation, batching,
Observable results, and subscriptions apply to each router. Direct adapter callers
can select a group with adapter.router('/users').
Based on 4.0.0 release source (4.0.0).