@connectrpc/validate
A Connect interceptor that validates RPC requests and responses using Protobuf constraints and CEL expressions.
Repository Health
Technical Analysis
@connectrpc/validate is a Connect interceptor that enforces validation constraints defined directly in your Protobuf schemas, removing the need for hand-written request-checking code. Built on protovalidate and the Common Expression Language (CEL), it lets you declare rules like email format or cross-field comparisons as schema options and have them enforced automatically at runtime, with no additional code generation step.
Dropping the interceptor into a Connect router or client validates unary and streaming RPCs alike, rejecting invalid requests with a typed InvalidArgument error and a structured violation detail. An optional validateResponses flag extends the same enforcement to outgoing responses, which is useful for catching handler bugs that would otherwise return schema-violating data to callers.
What You Get
- One-line interceptor -
createValidateInterceptor()drops into a Connect router or client’sinterceptorsarray with no other wiring. - Schema-driven constraints - validation rules live in the
.protofile itself viabuf.validateoptions, not in hand-written imperative checks. - Unary and streaming support - both unary calls and client/server/bidi streams are validated message-by-message as they arrive, without buffering.
- Optional response validation - a
validateResponsesflag extends the same enforcement to outgoing responses, usingCode.Internalinstead ofCode.InvalidArgument. - Structured violation details - failed validations return a
ConnectErrorcarrying a machine-readable violation payload, not just an error string.
Common Use Cases
- Guarding RPC handlers from malformed input - reject requests with invalid emails, out-of-range numbers, or failed cross-field checks before handler code runs.
- Cross-language validation consistency - since constraints live in the shared
.protoschema, Go, Java, and other Connect implementations enforce the identical rules. - Catching handler regressions - enabling
validateResponsesin tests or staging surfaces handlers that silently return schema-violating data. - Streaming input sanitization - validate each message in a client-streaming or bidi-streaming RPC as it’s received, rather than validating only after the full stream completes.
Under The Hood
Architecture
The package is a single-purpose middleware exposed through one exported factory, createValidateInterceptor() in src/interceptor.ts, which returns a standard Connect Interceptor (a higher-order function of the form (next) => (req) => ...). For unary calls it validates the request message before invoking next, and validates the response afterward when validateResponses is enabled. For streaming calls it wraps the request’s (and optionally the response’s) AsyncIterator, validating each message individually as it is pulled from the stream rather than buffering the whole stream first, and forwards return/throw on the wrapped iterator so cancellation semantics are preserved. src/index.ts is a thin re-export, keeping the entire module’s logic in one file with no internal layering beyond that.
Tech Stack
Written in TypeScript and built to both ESM and CommonJS outputs (build:cjs / build:esm via tsc), verified for correct type exports with @arethetypeswrong/cli. It depends on @connectrpc/connect (the interceptor host framework), @bufbuild/protobuf (message descriptors), and @bufbuild/protovalidate (the actual CEL-based constraint evaluation and violationsToProto conversion) as peer dependencies. The repo is a Turborepo monorepo with a sibling example package demonstrating real usage against @connectrpc/connect-node, Buf tooling (buf.gen.yaml/buf.lock) for generating code from proto/test.proto, and Biome for linting/formatting.
Code Quality
Tests in interceptor.test.ts use Node’s built-in node:test and node:assert/strict, exercising the interceptor through a real in-memory Connect router transport (createRouterTransport) rather than mocks, covering valid/invalid unary and streaming calls with and without response validation enabled. Errors are never swallowed — validation failures are explicitly wrapped as a typed ConnectError carrying a machine-readable violation payload. CI enforces formatting, license-header presence, linting with warnings-as-errors, package-export correctness, and a build step across a Node 20/22/24 test matrix, plus a diff-check that fails if generated files drift from committed ones.
API Design
The entire public surface is one factory function with two optional fields (validator, validateResponses), so adopting it is a single line added to an existing interceptors array. Constraints are authored declaratively in the Protobuf schema rather than as imperative validation code, meaning no additional code generation step is needed to get enforcement. Errors surface using Connect’s own Code enum (InvalidArgument for requests, Internal for responses) with structured detail payloads, so failures compose naturally with existing Connect error-handling code rather than requiring bespoke parsing.