gRPC
> gRPC server and HTTP gateway with context helpers, structured errors, and interceptor-friendly APIs.
The grpc package wraps google.golang.org/grpc with Velocity
conventions — context helpers for auth/request metadata, structured
error constructors, and a sibling HTTP gateway that exposes gRPC
services over REST via grpc-gateway.
Import path: github.com/velocitykode/velocity/grpc
Server
srv := grpc.NewServer(
grpc.WithPort("50051"),
grpc.WithEnvironment("production"), // force-disables reflection
grpc.WithReflection(true), // enable reflection in dev
grpc.WithMaxRecvMsgSize(4<<20),
grpc.WithMaxSendMsgSize(4<<20),
grpc.WithLogger(v.Log),
)Interceptors
Unary and stream interceptors follow standard gRPC signatures:
srv.Use(
authInterceptor,
loggingInterceptor,
recoveryInterceptor,
)
srv.UseStream(streamAuthInterceptor)Mixed pairs:
srv.UseAll(
grpc.InterceptorPair{Unary: authUnary, Stream: authStream},
)Registering services
srv.RegisterService(func(s any) {
pb.RegisterUsersServer(s.(*googleGrpc.Server), usersImpl)
})The closure receives the underlying *grpc.Server — use it to call
generated Register* functions.
Running
if err := srv.Build(); err != nil {
return err
}
if err := srv.Start(); err != nil { // blocks
return err
}Non-blocking:
if err := srv.StartAsync(); err != nil {
return err
}
// later
srv.GracefulStop()Shutdown(ctx) waits for in-flight RPCs with a deadline; Stop is
immediate.
HTTP gateway (grpc-gateway)
Expose the same service over REST:
gw := grpc.NewGateway(
grpc.GatewayWithPort("8080"),
grpc.GatewayWithGRPCEndpoint("localhost:50051"),
grpc.GatewayWithInsecure(), // or GatewayWithTLS(certFile)
grpc.GatewayWithLogger(v.Log),
)
gw.RegisterHandler(pb.RegisterUsersHandlerFromEndpoint)
if err := gw.Build(context.Background()); err != nil {
return err
}
// HTTP middleware chains applied to the gateway mux
gw.Use(cors.Default().Handler, requestLogger)
gw.StartAsync()gw.Mux() returns the underlying runtime.ServeMux if you need to
attach custom handlers alongside the generated ones.
Context helpers
Claims
A Claims value carries authenticated user data:
ctx = grpc.ContextWithClaims(ctx, Claims{UserID: 42, TeamID: 7})
claims := grpc.ClaimsFromContext(ctx) // safe: returns zero value if absent
claims := grpc.MustClaimsFromContext(ctx) // panics if absent — use in auth'd handlers
userID := grpc.UserIDFromContext(ctx) // shortcut
teamID := grpc.TeamIDFromContext(ctx)Attach them in an auth interceptor before the handler runs.
Request ID
ctx = grpc.ContextWithRequestID(ctx, grpc.GenerateRequestID())
id := grpc.RequestIDFromContext(ctx)Method name
ctx = grpc.ContextWithMethod(ctx, info.FullMethod)
method := grpc.MethodFromContext(ctx)Useful inside logging interceptors that run below the method dispatch.
Metadata helpers
token := grpc.ExtractBearerToken(ctx) // from "authorization: Bearer ..."
value := grpc.ExtractMetadata(ctx, "x-tenant-id")
values := grpc.ExtractAllMetadata(ctx, "x-forwarded-for")Errors
Typed constructors produce standard google.golang.org/grpc/status
errors:
return nil, grpc.NotFound("user not found")
return nil, grpc.NotFoundf("user %d", id)
return nil, grpc.InvalidArgument("id required")
return nil, grpc.Unauthenticated("missing token")
return nil, grpc.PermissionDenied("not your team")
return nil, grpc.AlreadyExists("duplicate email")
return nil, grpc.FailedPrecondition("invoice already finalized")
return nil, grpc.ResourceExhausted("quota exceeded")
return nil, grpc.Unavailable("downstream timeout")
return nil, grpc.Internal("bug")All wrap codes.Code under the hood.
Inspecting errors
if grpc.IsNotFound(err) {
// handle
}
switch grpc.Code(err) {
case codes.NotFound: // ...
case codes.InvalidArgument: // ...
}
msg := grpc.Message(err) // unwrap status message
status := grpc.FromError(err) // raw *status.StatusWrapping existing errors
if err := db.Find(&user); err != nil {
return nil, grpc.WrapError(err) // maps stdlib errors → grpc codes
}
return nil, grpc.WrapErrorWithCode(err, codes.FailedPrecondition)Reflection in production
When WithEnvironment("production") is set, WithReflection(true) is
silently ignored — reflection is force-disabled so gRPC service
introspection isn’t exposed to untrusted callers.
Health checks
The package includes a standard gRPC health service registration
helper — see health.go for RegisterHealthService(s *Server). Pair
it with your load balancer’s gRPC health probes.