35 Validating Requests Using Interceptors
When building modern backend applications with gRPC, ensuring the validity of request data is a crucial concern. Just like with REST, in gRPC we can use an interceptor to validate requests in a centralized way before they reach the core logic in the service handler.
This article walks through a case study of how to build a validation interceptor on a gRPC server in Golang — complete with code examples, a flow diagram, and a table of request scenarios.
The Interceptor Concept in gRPC
An interceptor in gRPC Go works much like middleware in Express. Interceptors can be attached to:
- Unary RPC: for ordinary request-response calls
- Streaming RPC: for incoming and outgoing data streams
Interceptors can be used for:
- Input validation
- Logging
- Monitoring
- Authentication
- Error handling
In this case study, we’ll focus on a Unary Interceptor for input validation.
Case Study: Validating the CreateUser Request
Request Schema
1message CreateUserRequest {
2 string name = 1;
3 string email = 2;
4 int32 age = 3;
5}
6
7message CreateUserResponse {
8 string message = 1;
9}
10
11service UserService {
12 rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
13}Validation Rules We Want:
namemust be a non-empty stringemailmust have a valid formatageis optional, but if present it must be >= 0
Implementing the Validation Interceptor
1. Interceptor Function
1func ValidationInterceptor(
2 ctx context.Context,
3 req interface{},
4 info *grpc.UnaryServerInfo,
5 handler grpc.UnaryHandler,
6) (interface{}, error) {
7
8 switch r := req.(type) {
9 case *pb.CreateUserRequest:
10 if r.GetName() == "" {
11 return nil, status.Error(codes.InvalidArgument, "Name is required")
12 }
13 if !isValidEmail(r.GetEmail()) {
14 return nil, status.Error(codes.InvalidArgument, "Email is invalid")
15 }
16 if r.Age < 0 {
17 return nil, status.Error(codes.InvalidArgument, "Age must be >= 0")
18 }
19 }
20
21 // continue to the handler
22 return handler(ctx, req)
23}
24
25func isValidEmail(email string) bool {
26 re := regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`)
27 return re.MatchString(email)
28}2. Registering the Interceptor with the Server
1s := grpc.NewServer(
2 grpc.UnaryInterceptor(ValidationInterceptor),
3)
4pb.RegisterUserServiceServer(s, &UserService{})gRPC Request Flow with the Validation Interceptor
sequenceDiagram
participant Client
participant gRPCServer
participant Interceptor
participant Handler
Client->>gRPCServer: CreateUser(request)
gRPCServer->>Interceptor: Validasi input
alt Valid
Interceptor->>Handler: Lanjut handler
Handler-->>Client: Response OK
else Tidak valid
Interceptor-->>Client: Error 400 (InvalidArgument)
end
Validation Scenario Table
| # | Request Payload | Response | Status |
|---|---|---|---|
| 1 | Name OK, email OK, age 20 | OK | OK |
| 2 | Empty name | Error: Name required | InvalidArgument |
| 3 | Invalid email | Error: Email invalid | InvalidArgument |
| 4 | Age -5 | Error: Age invalid | InvalidArgument |
| 5 | Empty email | Error: Email invalid | InvalidArgument |
Benefits of Using an Interceptor for Validation
- Separation of concerns (SoC): the handler deals only with the core logic
- Consistency: uniform validation across every handler
- Easy to test: the interceptor can be unit-tested
- Reusability: it can be extended to other endpoints
Conclusion
Validating requests with a gRPC interceptor in Golang is an efficient, clean, and idiomatic approach. With this technique, we can keep handlers clean and centralize all validation rules inside the interceptor, making the codebase more scalable and maintainable.
Use this approach in every RPC service that needs validation — and combine it with authentication, rate limiting, and logging to build a solid, professional backend system.