{"id":20872376,"url":"https://github.com/romnn/go-service","last_synced_at":"2026-04-14T12:33:12.242Z","repository":{"id":57539371,"uuid":"288718638","full_name":"romnn/go-service","owner":"romnn","description":"Utilities for building gRPC and HTTP services in Go","archived":false,"fork":false,"pushed_at":"2022-10-23T21:29:58.000Z","size":422,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-14T14:03:16.526Z","etag":null,"topics":["authentication","golang","grpc","http","microservice","protobuf","reflection"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/romnn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-19T11:54:46.000Z","updated_at":"2022-10-22T20:08:59.000Z","dependencies_parsed_at":"2023-01-20T09:20:53.331Z","dependency_job_id":null,"html_url":"https://github.com/romnn/go-service","commit_stats":null,"previous_names":["romnnn/go-grpc-service","romnn/go-grpc-service"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/romnn/go-service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romnn%2Fgo-service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romnn%2Fgo-service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romnn%2Fgo-service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romnn%2Fgo-service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/romnn","download_url":"https://codeload.github.com/romnn/go-service/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romnn%2Fgo-service/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28029230,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-25T02:00:05.988Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","golang","grpc","http","microservice","protobuf","reflection"],"created_at":"2024-11-18T06:18:54.404Z","updated_at":"2025-12-25T12:22:14.484Z","avatar_url":"https://github.com/romnn.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## go-service\n\n[![Build Status](https://github.com/romnn/go-service/workflows/test/badge.svg)](https://github.com/romnn/go-service/actions)\n[![GitHub](https://img.shields.io/github/license/romnn/go-service)](https://github.com/romnn/go-service)\n[![GoDoc](https://godoc.org/github.com/romnn/go-service?status.svg)](https://godoc.org/github.com/romnn/go-service)\n[![Test Coverage](https://codecov.io/gh/romnn/go-service/branch/master/graph/badge.svg)](https://codecov.io/gh/romnn/go-service)\n\nService utilities for building gRPC and HTTP services in Go.\n\nSome features:\n\n- composable authentication using JWT\n- gRPC interceptors for method reflection\n\n### Example: Authentication\n\n```proto\n// examples/auth/auth.proto\n\nsyntax = \"proto3\";\npackage auth;\n\nimport \"google/protobuf/timestamp.proto\";\n\nservice Auth {\n  rpc Login(LoginRequest) returns (AuthToken) {}\n  rpc Validate(ValidationRequest) returns (ValidationResult) {}\n}\n\nmessage LoginRequest {\n  string email = 1;\n  string password = 2;\n}\n\nmessage ValidationRequest { string token = 1; }\n\nmessage ValidationResult { bool valid = 1; }\n\nmessage AuthToken {\n  string token = 1;\n  string email = 2;\n  google.protobuf.Timestamp expires = 10;\n}\n\n```\n\n```go\n// examples/auth/server.go\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/golang-jwt/jwt/v4\"\n\tpb \"github.com/romnn/go-service/examples/auth/gen\"\n\t\"github.com/romnn/go-service/pkg/auth\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\n)\n\n// User represents a user\ntype User struct {\n\tEmail          string\n\tHashedPassword string\n}\n\n// UserDatabase is a mock user database\ntype UserDatabase interface {\n\tGetUserByEmail(email string) (*User, error)\n\tAddUser(user *User)\n\tRemoveUserByEmail(email string) (*User, error)\n}\n\ntype userDatabase struct {\n\tusers map[string]*User\n}\n\n// AddUser adds a user to the database\nfunc (db *userDatabase) AddUser(user *User) {\n\tdb.users[user.Email] = user\n}\n\n// RemoveUserByEmail removes a user\nfunc (db *userDatabase) RemoveUserByEmail(email string) (*User, error) {\n\tif user, ok := db.users[email]; ok {\n\t\tdelete(db.users, email)\n\t\treturn user, nil\n\t}\n\treturn nil, fmt.Errorf(\"no user with email %q\", email)\n}\n\n// GetUserByEmail gets a user\nfunc (db *userDatabase) GetUserByEmail(email string) (*User, error) {\n\tif user, ok := db.users[email]; ok {\n\t\treturn user, nil\n\t}\n\treturn nil, fmt.Errorf(\"no user with email %q\", email)\n}\n\n// AuthService ...\ntype AuthService struct {\n\tpb.UnimplementedAuthServer\n\tAuthenticator *auth.Authenticator\n\tDatabase      UserDatabase\n}\n\n// Claims encode the JWT token claims\ntype Claims struct {\n\tUserEmail string `json:\"user-email\"`\n\tjwt.RegisteredClaims\n}\n\n// GetRegisteredClaims returns the standard claims that will be set automatically\nfunc (claims *Claims) GetRegisteredClaims() *jwt.RegisteredClaims {\n\t// MUST return pointer to registered claims of this struct\n\treturn \u0026claims.RegisteredClaims\n}\n\n// Validate validates a token\nfunc (s *AuthService) Validate(ctx context.Context, in *pb.ValidationRequest) (*pb.ValidationResult, error) {\n\tvalid, token, err := s.Authenticator.Validate(in.GetToken(), \u0026Claims{})\n\tif err != nil {\n\t\tlog.Println(err)\n\t\treturn \u0026pb.ValidationResult{Valid: false}, status.Error(codes.Internal, \"Failed to validate token\")\n\t}\n\tif claims, ok := token.Claims.(*Claims); ok \u0026\u0026 valid {\n\t\tlog.Printf(\"valid authentication claims: %v\", claims)\n\t\treturn \u0026pb.ValidationResult{Valid: true}, nil\n\t}\n\treturn \u0026pb.ValidationResult{Valid: false}, nil\n}\n\n// Login logs in a user\nfunc (s *AuthService) Login(ctx context.Context, in *pb.LoginRequest) (*pb.AuthToken, error) {\n\tuser, err := s.Database.GetUserByEmail(in.GetEmail())\n\tif err != nil {\n\t\tlog.Println(err)\n\t\treturn nil, status.Error(codes.NotFound, \"no such user\")\n\t}\n\tif !auth.CheckPasswordHash(in.GetPassword(), user.HashedPassword) {\n\t\treturn nil, status.Error(codes.Unauthenticated, \"unauthorized\")\n\t}\n\n\t// authenticated\n\ttoken, err := s.Authenticator.SignJwtClaims(\u0026Claims{\n\t\tUserEmail: user.Email,\n\t})\n\tif err != nil {\n\t\tlog.Println(err)\n\t\treturn nil, status.Error(codes.Internal, \"error while signing token\")\n\t}\n\n\texpirationTime := time.Now().Add(s.Authenticator.ExpiresAfter)\n\treturn \u0026pb.AuthToken{\n\t\tToken:   token,\n\t\tEmail:   user.Email,\n\t\tExpires: timestamppb.New(expirationTime),\n\t}, nil\n}\n\nfunc main() {\n\tlistener, err := net.Listen(\"tcp\", \":8080\")\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to listen: %v\", err)\n\t}\n\n\tauthenticator := auth.Authenticator{\n\t\tExpiresAfter: 100 * time.Second,\n\t\tIssuer:       \"issuer@example.org\",\n\t\tAudience:     \"example.org\",\n\t}\n\n\tkeyConfig := auth.KeyConfig{Generate: true}\n\tif err := authenticator.SetupKeys(\u0026keyConfig); err != nil {\n\t\tlog.Fatalf(\"failed to setup keys: %v\", err)\n\t}\n\n\tservice := AuthService{\n\t\tAuthenticator: \u0026authenticator,\n\t\tDatabase: \u0026userDatabase{\n\t\t\tusers: make(map[string]*User),\n\t\t},\n\t}\n\n\tserver := grpc.NewServer()\n\tpb.RegisterAuthServer(server, \u0026service)\n\n\tshutdown := make(chan os.Signal, 1)\n\tsignal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)\n\tgo func() {\n\t\t\u003c-shutdown\n\t\tlog.Println(\"shutdown ...\")\n\t\tserver.GracefulStop()\n\t\tlistener.Close()\n\t}()\n\n\tlog.Printf(\"listening on: %v\", listener.Addr())\n\tif err := server.Serve(listener); err != nil {\n\t\tlog.Fatalf(\"failed to serve: %v\", err)\n\t}\n}\n\n```\n\n### Example: Reflection\n\n```proto\n// examples/reflect/reflect.proto\n\nsyntax = \"proto3\";\npackage reflect;\n\nimport \"google/protobuf/descriptor.proto\";\n\n// we define custom options\nextend google.protobuf.MethodOptions {\n  bool bool_value = 51234;\n  string string_value = 51235;\n  int32 int_value = 51236;\n}\n\nmessage Empty {}\n\nmessage Annotations {\n  bool bool_value = 1;\n  string string_value = 2;\n  int32 int_value = 3;\n}\n\nservice Reflect {\n  // we will read the options of this method using reflection\n  rpc GetNoAnnotations(Empty) returns (Annotations) {}\n\n  // we will read the options of this method using reflection\n  rpc GetAnnotations(Empty) returns (Annotations) {\n    option (bool_value) = true;\n    option (string_value) = \"Hello World\";\n    option (int_value) = 42;\n  }\n}\n\n```\n\n```go\n// examples/reflect/server.go\n\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\tpb \"github.com/romnn/go-service/examples/reflect/gen\"\n\t\"github.com/romnn/go-service/pkg/grpc/reflect\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// ReflectService implements the reflect service\ntype ReflectService struct {\n\tpb.UnimplementedReflectServer\n}\n\nfunc (s *ReflectService) getAnnotations(ctx context.Context) (*pb.Annotations, error) {\n\tinfo, ok := reflect.GetMethodInfo(ctx)\n\tif !ok {\n\t\treturn nil, status.Error(codes.Internal, \"failed to get grpc method info\")\n\t}\n\tvar annotations pb.Annotations\n\tmethodOptions := info.Method().Options()\n\tif boolValue, ok := proto.GetExtension(methodOptions, pb.E_BoolValue).(bool); ok {\n\t\tannotations.BoolValue = boolValue\n\t}\n\tif stringValue, ok := proto.GetExtension(methodOptions, pb.E_StringValue).(string); ok {\n\t\tannotations.StringValue = stringValue\n\t}\n\tif intValue, ok := proto.GetExtension(methodOptions, pb.E_IntValue).(int32); ok {\n\t\tannotations.IntValue = intValue\n\t}\n\treturn \u0026annotations, nil\n}\n\n// GetNoAnnotations returns the options of this GRPC method\nfunc (s *ReflectService) GetNoAnnotations(ctx context.Context, req *pb.Empty) (*pb.Annotations, error) {\n\treturn s.getAnnotations(ctx)\n}\n\n// GetAnnotations returns the options of this GRPC method\nfunc (s *ReflectService) GetAnnotations(ctx context.Context, req *pb.Empty) (*pb.Annotations, error) {\n\treturn s.getAnnotations(ctx)\n}\n\nfunc main() {\n\tlistener, err := net.Listen(\"tcp\", \":8080\")\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to listen: %v\", err)\n\t}\n\n\tservice := ReflectService{}\n\tregistry := reflect.NewRegistry()\n\tserver := grpc.NewServer(\n\t\tgrpc.ChainUnaryInterceptor(reflect.UnaryServerInterceptor(registry)),\n\t\tgrpc.ChainStreamInterceptor(reflect.StreamServerInterceptor(registry)),\n\t)\n\tpb.RegisterReflectServer(server, \u0026service)\n\tregistry.Load(server)\n\n\tshutdown := make(chan os.Signal, 1)\n\tsignal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)\n\tgo func() {\n\t\t\u003c-shutdown\n\t\tlog.Println(\"shutdown ...\")\n\t\tserver.GracefulStop()\n\t\tlistener.Close()\n\t}()\n\n\tlog.Printf(\"listening on: %v\", listener.Addr())\n\tif err := server.Serve(listener); err != nil {\n\t\tlog.Fatalf(\"failed to serve: %v\", err)\n\t}\n}\n\n```\n\nFor more examples, see `examples/`.\n\n#### Development\n\n##### Tooling\n\nBefore you get started, make sure you have installed the following tools:\n\n    $ python3 -m pip install pre-commit bump2version invoke\n    $ go install golang.org/x/tools/cmd/goimports\n    $ go install golang.org/x/lint/golint\n    $ go install github.com/fzipp/gocyclo\n\nIt is advised to install the git commit hooks to enforce code checks:\n\n```bash\ninv install-hooks\n```\n\nTo check if all checks pass:\n\n```bash\ninv pre-commit\n```\n\n##### Compiling proto files\n\nIf you want to (re-)compile the sample grpc `.proto` services, you will need `protoc`, `protoc-gen-go` and `protoc-gen-go-grpc`.\n\n```bash\napt install -y protobuf-compiler\nbrew install protobuf\n\ngo get -u google.golang.org/protobuf/cmd/protoc-gen-go\ngo install google.golang.org/protobuf/cmd/protoc-gen-go\n\ngo get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc\ngo install google.golang.org/grpc/cmd/protoc-gen-go-grpc\n```\n\nTo compile, you can use the provided utility:\n\n```bash\ninv compile-protos\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromnn%2Fgo-service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromnn%2Fgo-service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromnn%2Fgo-service/lists"}