JavaScript/TypeScript quickstart#
Getting started with Protovalidate is simple if you're familiar with Buf—otherwise, you may want to follow the step-by-step example.
- Add buf.build/bufbuild/protovalidate to
buf.yamlthenbuf dep update. - Add validation rules and generate code.
- Install Protovalidate:
npm install @bufbuild/protovalidate. - Validate Protobuf messages:
- Validate RPC requests with @connectrpc/validate.
Step-by-step example#
Start by setting up the example project:
- Install the Buf CLI. If you already have, run
buf --versionto verify that you're using at least1.54.0. - Have git and Node.js installed.
-
Clone the
buf-examplesrepository: -
Open a terminal to the repository and navigate to
protovalidate/quickstart-es/start.
The quickstart code contains Buf CLI configuration files (buf.yaml, buf.gen.yaml), a simple weather_service.proto, and an idiomatic unit test.
Add Protovalidate to schemas#
Depend on Protovalidate#
Protovalidate is available through the Buf Schema Registry and provides the Protobuf extensions, options, and messages powering validation.
Add it as a dependency in buf.yaml:
version: v2
modules:
- path: proto
+ deps:
+ - buf.build/bufbuild/protovalidate
lint:
use:
- STANDARD
breaking:
use:
- FILE
Next, update dependencies. You may see a warning that Protovalidate hasn't yet been used. That's fine.
$ buf dep update
WARN Module buf.build/bufbuild/protovalidate is declared in your buf.yaml deps but is unused...
Add rules to a message#
Open proto/bufbuild/weather/v1/weather_service.proto, import Protovalidate, and add validation rules to GetWeatherRequest.
syntax = "proto3";
package bufbuild.weather.v1;
+ import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";
// GetWeatherRequest is a request for weather at a point on Earth.
message GetWeatherRequest {
// latitude must be between -90 and 90, inclusive, to be valid. Use of a
// float allows precision to about one square meter.
- float latitude = 1;
+ float latitude = 1 [
+ (buf.validate.field).float.gte = -90,
+ (buf.validate.field).float.lte = 90
+ ];
// longitude must be between -180 and 180, inclusive, to be valid. Use of a
// float allows precision to about one square meter.
- float longitude = 2;
+ float longitude = 2 [
+ (buf.validate.field).float.gte = -180,
+ (buf.validate.field).float.lte = 180
+ ];
// forecast_date for the weather request. It must be within the next
// three days.
google.protobuf.Timestamp forecast_date = 3;
}
Try it in the Playground#
Experiment with Protovalidate rules in the Protovalidate playground—modify this example, try out a custom CEL rule, or write your own validation logic without any local setup.
Lint your changes#
Some rule combinations compile successfully but fail at runtime. This example requires latitude but also skips its validation when it has its zero value, creating a logical contradiction:
message GetWeatherRequest {
float latitude = 1 [
(buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE,
(buf.validate.field).required = true,
(buf.validate.field).float.gte = -90,
(buf.validate.field).float.lte = 90
];
}
buf lint identifies these and other problems, like invalid CEL expressions, with its PROTOVALIDATE rule :
$ buf lint
proto/bufbuild/weather/v1/weather_service.proto:29:5:Field "latitude" has both
(buf.validate.field).required and (buf.validate.field).ignore=IGNORE_IF_ZERO_VALUE.
A field cannot be empty if it is required.
Run buf lint whenever you edit your schemas and in GitHub Actions or other CI/CD tools.
Build the module#
Now that you've added Protovalidate as a dependency, updated your schema with rules, and validated changes with buf lint, your module should build with no errors:
Generate code#
With Protovalidate, you don't need any new code generation plugins: its rules are compiled as part of your message descriptors.
Include imports#
You'll need to generate code for Protovalidate.
Update the settings for the bufbuild/es plugin in buf.gen.yaml, adding include_imports: true:
version: v2
clean: true
inputs:
- directory: proto
plugins:
- remote: buf.build/bufbuild/es:v2.5.1
out: src/gen
opt:
- target=ts
- import_extension=js
+ include_imports: true
Run buf generate to include your new rules in the GetWeatherRequest descriptor:
To learn more about generating code with the Buf CLI, read the code generation overview.
Add business logic with CEL#
Real world validation rules are often complicated and need more than a simple set of static rules:
- A
BuyMovieTicketsRequestrequest must be for ashowtimein the future but no more than two weeks in the future. - A
CreateTeamRequestwith repeatedmembersmust ensure all email addresses are unique across the team. - A
ScheduleMeetingRequestmust have astart_timebefore itsend_time, and the meeting duration can't exceed 8 hours.
Protovalidate can meet all of these requirements because all Protovalidate rules are defined in Common Expression Language (CEL).
CEL is a lightweight, high-performance expression language that allows expressions like this.first_flight_duration + this.second_flight_duration < duration('48h') to evaluate consistently across languages.
Adding a CEL-based rule to a field is straightforward.
Instead of a providing a static value, you provide a unique identifier (id), an error message, and a CEL expression.
Building on the prior GetWeatherRequest example, add a custom rule stating that users must ask for weather forecasts within the next 72 hours:
syntax = "proto3";
package bufbuild.weather.v1;
import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";
// GetWeatherRequest is a request for weather at a point on Earth.
message GetWeatherRequest {
// latitude must be between -90 and 90, inclusive, to be valid. Use of a
// float allows precision to about one square meter.
float latitude = 1 [
(buf.validate.field).float.gte = -90,
(buf.validate.field).float.lte = 90
];
// longitude must be between -180 and 180, inclusive, to be valid. Use of a
// float allows precision to about one square meter.
float longitude = 2 [
(buf.validate.field).float.gte = -180,
(buf.validate.field).float.lte = 180
];
// forecast_date for the weather request. It must be within the next
// three days.
- google.protobuf.Timestamp forecast_date = 3;
+ google.protobuf.Timestamp forecast_date = 3 [(buf.validate.field).cel = {
+ id: "forecast_date.within_72_hours"
+ message: "Forecast date must be in the next 72 hours."
+ expression: "this >= now && this <= now + duration('72h')"
+ }];
}
Remember to recompile and regenerate code:
Run validation#
The example code has a failing test (weather.test.js).
Let's get it to pass, using Protovalidate's API to validate sample messages.
-
Navigate to
protovalidate/quickstart-es/startwithin thebuf-examplesrepository. -
Install Protovalidate using
npm. -
Run
src/weather.test.tswithnpm test. It should fail: it expects invalid latitudes and longitudes to be rejected, but you haven't yet added any validation. -
Open
src/weather.tsand make the following modifications.src/weather.ts+ import { createValidator, type ValidationResult } from "@bufbuild/protovalidate"; import { GetWeatherRequest, + GetWeatherRequestSchema } from "./gen/bufbuild/weather/v1/weather_service_pb.js"; + const validator = createValidator(); - export function validateWeather(msg: GetWeatherRequest): any { + export function validateWeather(msg: GetWeatherRequest): ValidationResult { - // TODO: validate the request + return validator.validate(GetWeatherRequestSchema, msg); } -
Run
npm test. Now that you've added validation, all tests should pass.
You've now walked through the basic steps for using Protovalidate: adding it as a dependency, annotating your schemas with rules, and validating Protobuf messages.
Validate RPC requests#
One of Protovalidate's most common use cases is for validating requests made to RPCs. Use the @connectrpc/validate interceptor for automatic validation across your APIs.
Next steps#
Read on to learn more about enabling schema-first validation with Protovalidate:
- Review Protovalidate's library of ready-to-use standard rules.
- Learn how to write custom validation rules with Common Expression Language.
- Explore how Protovalidate works in advanced CEL topics.