Skip to main content
Kratos provides custom protoc plugins to generate HTTP bindings and error definitions from Protocol Buffer files.

Overview

Kratos code generation includes:
  • protoc-gen-go-http: Generates HTTP server and client code from protobuf with Google API annotations
  • protoc-gen-go-errors: Generates type-safe error definitions from enums
  • Standard protoc plugins for gRPC and protobuf messages

Installation

1
Install Protocol Buffer Compiler
2
Install protoc from protobuf releases:
3
# macOS
brew install protobuf

# Linux
apt install -y protobuf-compiler

# Verify installation
protoc --version
4
Install Protoc Plugins
5
Install all required protoc plugins:
6
# Standard Go protobuf plugin
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

# gRPC plugin
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Kratos HTTP plugin
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest

# Kratos errors plugin
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest

# OpenAPI plugin (optional)
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
7
Verify Installation
8
Check that plugins are in your PATH:
9
which protoc-gen-go-http
which protoc-gen-go-errors

Using protoc-gen-go-http

The HTTP plugin generates server routing and client code from protobuf definitions.

Basic Usage

Generate HTTP bindings from a protobuf file:
protoc --proto_path=. \
  --proto_path=./third_party \
  --go_out=paths=source_relative:. \
  --go-http_out=paths=source_relative:. \
  api/helloworld/v1/*.proto

Generated Code Structure

For this protobuf definition:
api/helloworld/v1/greeter.proto
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/helloworld/{name}"
    };
  }
}
The plugin generates greeter_http.pb.go with:
// HTTP Server Registration
func RegisterGreeterHTTPServer(s *http.Server, srv GreeterHTTPServer) {
  r := s.Route("/")
  r.GET("/helloworld/{name}", _Greeter_SayHello0_HTTP_Handler(srv))
}

// HTTP Handler
func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Context) error {
  return func(ctx http.Context) error {
    var in HelloRequest
    if err := ctx.BindVars(&in); err != nil {
      return err
    }
    http.SetOperation(ctx, "/helloworld.v1.Greeter/SayHello")
    h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
      return srv.SayHello(ctx, req.(*HelloRequest))
    })
    out, err := h(ctx, &in)
    if err != nil {
      return err
    }
    reply := out.(*HelloReply)
    return ctx.Result(200, reply)
  }
}

HTTP Method Mapping

The plugin supports all HTTP methods:
service ProductService {
  // GET request
  rpc GetProduct (GetProductRequest) returns (Product) {
    option (google.api.http) = {
      get: "/v1/products/{id}"
    };
  }
  
  // POST request
  rpc CreateProduct (CreateProductRequest) returns (Product) {
    option (google.api.http) = {
      post: "/v1/products"
      body: "*"
    };
  }
  
  // PUT request
  rpc UpdateProduct (UpdateProductRequest) returns (Product) {
    option (google.api.http) = {
      put: "/v1/products/{id}"
      body: "*"
    };
  }
  
  // PATCH request
  rpc PatchProduct (PatchProductRequest) returns (Product) {
    option (google.api.http) = {
      patch: "/v1/products/{id}"
      body: "*"
    };
  }
  
  // DELETE request
  rpc DeleteProduct (DeleteProductRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      delete: "/v1/products/{id}"
    };
  }
}

Path Variable Binding

The plugin automatically binds path variables to request fields:
rpc GetArticle (GetArticleRequest) returns (Article) {
  option (google.api.http) = {
    get: "/v1/authors/{author_id}/articles/{article_id}"
  };
}

message GetArticleRequest {
  string author_id = 1;   // Bound from path
  string article_id = 2;  // Bound from path
}

Body Binding

Control how request bodies are bound:
// Entire request as body
rpc CreateUser (CreateUserRequest) returns (User) {
  option (google.api.http) = {
    post: "/v1/users"
    body: "*"  // All fields in body
  };
}

// Specific field as body
rpc UpdateProfile (UpdateProfileRequest) returns (Profile) {
  option (google.api.http) = {
    put: "/v1/users/{user_id}/profile"
    body: "profile"  // Only profile field in body
  };
}

message UpdateProfileRequest {
  string user_id = 1;  // From path
  Profile profile = 2; // From body
}

Response Body Customization

Customize the response body:
rpc GetUserStats (GetUserStatsRequest) returns (GetUserStatsResponse) {
  option (google.api.http) = {
    get: "/v1/users/{user_id}/stats"
    response_body: "stats"  // Only return the stats field
  };
}

message GetUserStatsResponse {
  UserStats stats = 1;
  Metadata metadata = 2;  // Not included in response
}

Using protoc-gen-go-errors

The errors plugin generates type-safe error constructors from enum definitions.

Basic Usage

Generate error definitions:
protoc --proto_path=. \
  --proto_path=./third_party \
  --go_out=paths=source_relative:. \
  --go-errors_out=paths=source_relative:. \
  api/helloworld/v1/errors.proto

Define Error Enums

Create an errors protobuf file:
api/helloworld/v1/errors.proto
syntax = "proto3";

package helloworld.v1;

option go_package = "helloworld/api/helloworld/v1;v1";

import "errors/errors.proto";

enum ErrorReason {
  // Set default HTTP status code
  option (errors.default_code) = 500;
  
  // Define specific errors with HTTP codes
  USER_NOT_FOUND = 0 [(errors.code) = 404];
  USER_ALREADY_EXISTS = 1 [(errors.code) = 409];
  INVALID_ARGUMENT = 2 [(errors.code) = 400];
  UNAUTHORIZED = 3 [(errors.code) = 401];
  PERMISSION_DENIED = 4 [(errors.code) = 403];
  INTERNAL_ERROR = 5 [(errors.code) = 500];
}

Generated Error Code

The plugin generates helper functions:
api/helloworld/v1/errors.pb.go
// Check if error is of specific type
func IsUserNotFound(err error) bool {
  if err == nil {
    return false
  }
  e := errors.FromError(err)
  return e.Reason == ErrorReason_USER_NOT_FOUND.String() && e.Code == 404
}

// Create new error
func ErrorUserNotFound(format string, args ...interface{}) *errors.Error {
  return errors.New(404, ErrorReason_USER_NOT_FOUND.String(), fmt.Sprintf(format, args...))
}

// Other generated functions
func IsUserAlreadyExists(err error) bool
func ErrorUserAlreadyExists(format string, args ...interface{}) *errors.Error
// ... etc

Using Generated Errors

Use the generated functions in your service:
func (s *UserService) GetUser(ctx context.Context, req *v1.GetUserRequest) (*v1.User, error) {
  user, err := s.repo.FindUser(ctx, req.Id)
  if err != nil {
    if errors.Is(err, gorm.ErrRecordNotFound) {
      return nil, v1.ErrorUserNotFound("user %s not found", req.Id)
    }
    return nil, v1.ErrorInternalError("failed to get user: %v", err)
  }
  return user, nil
}

func (s *UserService) CreateUser(ctx context.Context, req *v1.CreateUserRequest) (*v1.User, error) {
  exists, _ := s.repo.UserExists(ctx, req.Email)
  if exists {
    return nil, v1.ErrorUserAlreadyExists("user with email %s already exists", req.Email)
  }
  // ... create user
}

Makefile Integration

Create a Makefile for easy code generation:
Makefile
.PHONY: api
api:
	@echo "Generating API code..."
	protoc --proto_path=. \
	       --proto_path=./third_party \
	       --go_out=paths=source_relative:. \
	       --go-http_out=paths=source_relative:. \
	       --go-grpc_out=paths=source_relative:. \
	       --go-errors_out=paths=source_relative:. \
	       api/**/*.proto
	@echo "API code generated successfully"

.PHONY: openapi
openapi:
	protoc --proto_path=. \
	       --proto_path=./third_party \
	       --openapi_out=fq_schema_naming=true,default_response=false:. \
	       api/**/*.proto
Run generation:
make api
make openapi

Configuration File

Use a configuration file for complex setups:
buf.gen.yaml
version: v1
plugins:
  - name: go
    out: .
    opt:
      - paths=source_relative
  - name: go-grpc
    out: .
    opt:
      - paths=source_relative
  - name: go-http
    out: .
    opt:
      - paths=source_relative
  - name: go-errors
    out: .
    opt:
      - paths=source_relative

Best Practices

Version Control

Commit generated code to version control for reproducibility

CI/CD Integration

Run code generation in CI to ensure it’s always up to date

Code Reviews

Review generated code changes to catch API breaking changes

Documentation

Generate OpenAPI specs for API documentation

Troubleshooting

Ensure third_party directory contains Google API protos:
git clone https://github.com/googleapis/googleapis third_party/googleapis
Verify plugins are in PATH and executable:
export PATH=$PATH:$(go env GOPATH)/bin
Check the go_package option in your proto files matches your module structure.

Next Steps

Service Implementation

Implement your generated service interfaces

Testing

Learn how to test generated code

Build docs developers (and LLMs) love