Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
27 Jun 2025 · 5 min read ·Article 19 / 110
Go

19 Adding a Unary Interceptor on the Server

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

When building a gRPC-based backend application, one of the powerful concepts you absolutely need to master is the interceptor. On the server side in particular, the unary interceptor plays a crucial role—covering everything from logging and authentication to metrics. In this article, we’ll thoroughly explore how to add a unary interceptor to a gRPC server, complete with real-world scenarios, code examples, and even a flow diagram so you can easily put it into practice.


What Is a gRPC Interceptor?

By analogy, an interceptor in gRPC is similar to “middleware” in ExpressJS or the HTTP pipeline in ASP.NET Core. An interceptor in gRPC lets us “short-circuit” or “extend” the process before or after an RPC executes in a generic way.

Danger
A Unary Interceptor handles a single-shot request (single request, single response), unlike a stream interceptor.

Why Do You Need a Unary Interceptor?

A unary interceptor is a great fit for several use cases:

  • Logging: Record every request, response, and error.
  • Authentication & Authorization: Validate a token/JWT before the handler executes.
  • Monitoring: Send metrics to Prometheus, DataDog, and so on.
  • Request/Response Transformation: Modify data before or after the handler.

This way, your business code doesn’t get mixed up with other concerns—keeping it clean, DRY, and testable.


Unary Interceptor Flow Diagram

To clarify how data flows when a unary interceptor runs, take a look at the diagram below:

MERMAID
flowchart LR
    A(Client) --> B(gRPC Server)
    B --> C(Interceptor)
    C --> D(Service Handler)
    D --> C
    C --> B
    B --> A
In the flow above, the Interceptor sits between the client request and the main handler, allowing us to inject additional logic before and after the core handler runs.


Example Scenario: A Logging Interceptor on the Server

Suppose our application has a simple service: a HelloService with a SayHello method.

1. Proto Definition (hello.proto)

proto
 1syntax = "proto3";
 2
 3service HelloService {
 4  rpc SayHello (HelloRequest) returns (HelloReply) {}
 5}
 6
 7message HelloRequest {
 8  string name = 1;
 9}
10
11message HelloReply {
12  string message = 1;
13}

Next step: generate the stub code for the server.


2. gRPC Server Implementation + Unary Interceptor

We’ll assume you’re using Go and the google.golang.org/grpc package, since its documentation and community are very extensive.

a. Creating the Logging Interceptor

go
 1import (
 2    "context"
 3    "log"
 4
 5    "google.golang.org/grpc"
 6)
 7
 8func LoggingUnaryInterceptor(
 9    ctx context.Context,
10    req interface{},
11    info *grpc.UnaryServerInfo,
12    handler grpc.UnaryHandler,
13) (resp interface{}, err error) {
14    log.Printf("Received request for method: %s", info.FullMethod)
15    resp, err = handler(ctx, req) // Run the actual handler
16    if err != nil {
17        log.Printf("Error executing %s: %v", info.FullMethod, err)
18    } else {
19        log.Printf("Response: %+v", resp)
20    }
21    return resp, err
22}

b. Registering the Interceptor on the Server

go
 1import (
 2    "google.golang.org/grpc"
 3    pb "path/to/generated/hello" // adjust the path
 4)
 5
 6func main() {
 7    server := grpc.NewServer(
 8        grpc.UnaryInterceptor(LoggingUnaryInterceptor), // Attach the interceptor here
 9    )
10    pb.RegisterHelloServiceServer(server, &HelloService{})
11    // ... bind the listener, etc.
12}

c. Implementing the Main Handler

go
1type HelloService struct {
2    pb.UnimplementedHelloServiceServer
3}
4
5func (s *HelloService) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
6    return &pb.HelloReply{Message: "Hello, " + req.Name + "!"}, nil
7}

Simulation: Logging Output

The table below shows a simulation of the interceptor’s output when a client sends a request:

MethodRequestResponseError
/HelloService/SayHello{ “name”: “Kiki” }{ “message”: “Hello, Kiki!” }-

Log output (stdout):

text
1Received request for method: /HelloService/SayHello
2Response: {Message:"Hello, Kiki!"}

Combining Multiple Interceptors

What if we want to stack several interceptors (for example: authentication + logging)? In Go, use grpc.ChainUnaryInterceptor:

go
1server := grpc.NewServer(
2    grpc.ChainUnaryInterceptor(
3        AuthenticationInterceptor,
4        LoggingUnaryInterceptor,
5        // and so on.
6    ),
7)

The chain runs in order; the final handler always stays at the very end.


Another Case Study: Authentication Interceptor

As a complement, here’s a simple interceptor for validating the authorization header.

go
 1func AuthUnaryInterceptor(
 2    ctx context.Context,
 3    req interface{},
 4    info *grpc.UnaryServerInfo,
 5    handler grpc.UnaryHandler,
 6) (interface{}, error) {
 7    md, ok := metadata.FromIncomingContext(ctx)
 8    if !ok || len(md["authorization"]) == 0 {
 9        return nil, status.Error(codes.Unauthenticated, "missing auth token")
10    }
11    token := md["authorization"][0]
12    if token != "mysecrettoken" {
13        return nil, status.Error(codes.Unauthenticated, "invalid auth token")
14    }
15    return handler(ctx, req)
16}

If the client doesn’t include the authorization header, the request is automatically rejected without the main handler ever being executed.


Best Practice: When Should You Use an Interceptor?

Middleware NeedGood Fit for an Interceptor?
Access Logging
Rate Limiting
Caching a Single Endpoint✗ (better handled inline in the handler)
Global Authentication
Service-level Validation
Simple Data Enrichment✗ (prefer the handler)

Conclusion

By adding a unary interceptor to your gRPC server, you can apply elegant, powerful, enterprise-grade middleware while keeping your code clean. The setup is straightforward, yet the impact is massive for the maintainability and scalability of your backend microservices.

If you come across an interesting use case, feel free to experiment by adding your own interceptors—and don’t be afraid to combine several at once. This is one of the modern backend service best practices that today’s engineers absolutely need to master!


Further Reading:


Hope this article helps! Don’t hesitate to ask in the comments or share your experience using interceptors in production.

Related Articles

💬 Comments