The Real Question
It's not "which is faster." It's "which fits your system's communication pattern."
Both REST and gRPC are valid choices in Go microservices. The wrong choice creates friction for years.
When gRPC Wins
1. Internal service-to-service communication
gRPC uses HTTP/2 with binary Protobuf encoding. For internal calls, this means:
- 3-10x smaller payloads vs JSON
- Strongly typed contracts via
.protofiles - Bidirectional streaming out of the box
service MetricsService { rpc StreamMetrics (StreamRequest) returns (stream Metric); rpc PushBatch (BatchRequest) returns (BatchResponse); }
func (s *server) StreamMetrics(req *pb.StreamRequest, stream pb.MetricsService_StreamMetricsServer) error { ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop() for { select { case <-stream.Context().Done(): return nil case <-ticker.C: m := collectMetric(req.DeviceId) if err := stream.Send(m); err != nil { return err } } } }
This is exactly the pattern we use for industrial IoT telemetry — devices stream metrics in real time, and the backend fans them out to multiple consumers.
When REST Wins
1. Public APIs and third-party integrations
Clients don't always support gRPC. Browsers don't (without gRPC-Web). REST with OpenAPI gives you:
- Universal client support
- Self-documenting contracts
- Easy debugging with curl/Postman
2. Simple CRUD services
If a service is just reading and writing data without streaming needs, REST keeps things simple:
func (h *Handler) CreateProject(w http.ResponseWriter, r *http.Request) { var req CreateProjectRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } project, err := h.svc.Create(r.Context(), req) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } json.NewEncoder(w).Encode(project) }
The Hybrid Approach
In production systems we typically run both:
| Layer | Protocol | Reason | |-------|----------|--------| | External API gateway | REST/HTTP | Browser + third-party clients | | Internal microservices | gRPC | Performance + type safety | | IoT device ingestion | gRPC streaming | High-frequency telemetry | | Webhooks / callbacks | REST | Universal compatibility |
Key Takeaways
- gRPC for internal: contracts, streaming, performance
- REST for external: compatibility, discoverability
- Don't mix both for the same service — pick per service boundary
- Protobuf schema evolution requires discipline; REST JSON is more forgiving