Implementing a Server
Three steps, always the same
Section titled “Three steps, always the same”Codegen gave you a server interface with one method per rpc. Implementing a server is always the same three steps:
- Implement the handler — write the body of
GetUser: take the request, return the response (or an error). - Register your implementation on a gRPC server object.
- Serve — bind to a port and start accepting HTTP/2 connections.
flowchart LR client["client stub"] -->|GetUserRequest| server["gRPC server (listening on :50051)"] server -->|dispatch by method| handler["your GetUser handler"] handler -->|User| server server -->|response| client
The gRPC server handles all the HTTP/2, protobuf decoding, and method dispatch. Your handler only ever sees a typed request and returns a typed response.
A complete unary server
Section titled “A complete unary server”Here is the whole thing — handler, registration, and serving — for UserService.GetUser:
type server struct { userv1.UnimplementedUserServiceServer // forward-compat embedding}
// GetUser is the handler: request in, response out.func (s *server) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.User, error) { // (normally you'd look this up in a database) return &userv1.User{ Id: req.Id, Name: "Ada Lovelace", }, nil}
func main() { lis, _ := net.Listen("tcp", ":50051") s := grpc.NewServer() userv1.RegisterUserServiceServer(s, &server{}) // register log.Println("listening on :50051") s.Serve(lis) // serve}class UserService(user_pb2_grpc.UserServiceServicer): # GetUser is the handler: request in, response out. def GetUser(self, request, context): return user_pb2.User( id=request.id, name="Ada Lovelace", )
def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server) # register server.add_insecure_port("[::]:50051") server.start() # serve print("listening on :50051") server.wait_for_termination()// GetUser is the handler: request in, callback out.const userService = { getUser(call, callback) { callback(null, { id: call.request.id, name: 'Ada Lovelace', }); },};
const server = new Server();server.addService(UserServiceService, userService); // registerserver.bindAsync('0.0.0.0:50051', ServerCredentials.createInsecure(), () => { console.log('listening on :50051'); // serve});Returning errors, not exceptions
Section titled “Returning errors, not exceptions”When something goes wrong, you do not throw a random exception — you return a gRPC status. A missing user is NOT_FOUND, bad input is INVALID_ARGUMENT. The error model has its own lesson in the Service Definition module; the key habit to start now is: map failures to status codes deliberately, because that status is what the client’s err will carry.
One handler, many concurrent calls
Section titled “One handler, many concurrent calls”Because the server rides on HTTP/2 multiplexing, many calls hit your handler concurrently over the same connections. In Go each call runs in its own goroutine; in Python a thread pool serves them; in Node the event loop interleaves them. That means your handler must be safe to run concurrently — no unsynchronized shared mutable state.