107. Studi Kasus: Menyambungkan Frontend gRPC-Web (JavaScript) dengan Backend Go
Dalam beberapa tahun terakhir, pola komunikasi antara aplikasi frontend dan backend terus berevolusi. REST API memang menjadi primadona, namun pola tersebut mulai “direcoki” oleh protokol komunikasi yang lebih efisien dan modern bernama gRPC. Salah satu kekuatan gRPC adalah dukungannya pada protobuf (Protocol Buffers) yang memungkinkan komunikasi data antar-generate code dengan performa prima.
Namun, adopsi gRPC pada frontend web tidak bisa langsung semulus pada mobile atau backend. Browser tidak mendukung langsung HTTP/2–protokol yang menjadi standar untuk gRPC murni. Di sinilah gRPC-Web hadir.
Artikel kali ini membahas studi kasus konkret: menyambungkan frontend JavaScript (React) yang menggunakan gRPC-Web, ke backend Go yang menjalankan server gRPC konvensional. Saya akan bagikan alur langkah-demi-langkah, studi kode, simulasi request, dan praktik baik dalam pengembangan ini.
Mengapa gRPC-Web?
Sebelum menyelam ke kode, mari kita bahas sebentar kenapa kita perlu repot-repot menggunakan gRPC dan bukan sekadar REST:
| REST | gRPC |
|---|---|
| Data format: text (JSON) | Data format: Binary (protobuf) |
| Overhead serialization | Sangat efisien, extensible serialization |
| No contract direk | Strictly defined via .proto (contract) |
| HTTP/1.1 | HTTP/2 (lebih cepat, multiplexed) |
| Kurang ideal untuk streaming | Mendukung full-duplex streaming |
| Mudah diakses di browser | Butuh tambahan (gRPC-Web) |
Pada aplikasi single-page modern, pengiriman dan penerimaan data besar, up-to-date, dan efisien menjadi penting. gRPC di sisi backend sangat memudahkan, namun frontend berbasis browser membutuhkan penyesuaian via gRPC-Web.
Diagram Arsitektur
Sebelum kode, inilah skema alurnya:
flowchart TD
Client[gRPC-Web aplikasi JavaScript (Browser)]
-->|request (HTTP/1.1)| Envoy[gRPC-Web Proxy (Envoy)]
-->|translasi ke HTTP/2| GoServer[gRPC Server (Go)]
GoServer
-->|response (HTTP/2)| Envoy
-->|HTTP/1.1 response| Client
Jadi, alur datanya adalah:
- JavaScript client (browser) => request via gRPC-Web (HTTP/1.1).
- Proxy
Envoyatau plugin lain translasi request ke HTTP/2 (untuk backend gRPC Go). - Response dikembalikan ke browser.
Persiapan: Definisikan Contract (.proto)
Kunci sukses gRPC adalah single source of truth dalam format .proto. Berikut contoh sederhana di folder proto/greet.proto:
1syntax = "proto3";
2
3package greet;
4
5service Greeter {
6 rpc SayHello (HelloRequest) returns (HelloReply) {}
7}
8
9message HelloRequest {
10 string name = 1;
11}
12
13message HelloReply {
14 string message = 1;
15}Properti seperti inilah yang mendasari kedua sisi—frontend & backend—agar selalu sinkron tanpa “nyontek” API!
1. Setup Backend gRPC di Go
A. Generate Protobuf
Instalasi tools untuk Go biasanya lewat:
1go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
2go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestCompile:
1protoc -I proto/ \
2 --go_out=pb --go_opt=paths=source_relative \
3 --go-grpc_out=pb --go-grpc_opt=paths=source_relative \
4 proto/greet.protoB. Implementasi Service di Go
File: main.go
1package main
2
3import (
4 "context"
5 "log"
6 "net"
7 "google.golang.org/grpc"
8 pb "your-module/pb/greet"
9)
10
11type server struct {
12 pb.UnimplementedGreeterServer
13}
14
15func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
16 return &pb.HelloReply{Message: "Hello, " + req.Name + "!"}, nil
17}
18
19func main() {
20 lis, err := net.Listen("tcp", ":50051")
21 if err != nil {
22 log.Fatalf("Failed to listen: %v", err)
23 }
24 s := grpc.NewServer()
25 pb.RegisterGreeterServer(s, &server{})
26 log.Println("gRPC Go server running on :50051")
27 if err := s.Serve(lis); err != nil {
28 log.Fatalf("Failed to serve: %v", err)
29 }
30}2. Setup Proxy gRPC-Web (Envoy)
Karena browser hanya bisa HTTP/1.1 + CORS, kita perlu proxy. Salah satu opsi yang battle-proven adalah Envoy. Contoh konfigurasi minimal (envoy.yaml):
1static_resources:
2 listeners:
3 - address:
4 socket_address:
5 address: 0.0.0.0
6 port_value: 8080
7 filter_chains:
8 - filters:
9 - name: envoy.filters.network.http_connection_manager
10 typed_config:
11 "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
12 codec_type: auto
13 stat_prefix: ingress_http
14 route_config:
15 name: local_route
16 virtual_hosts:
17 - name: backend
18 domains: ["*"]
19 routes:
20 - match:
21 prefix: "/"
22 route:
23 cluster: greeter_service
24 http_filters:
25 - name: envoy.filters.http.grpc_web
26 - name: envoy.filters.http.cors
27 - name: envoy.filters.http.router
28 clusters:
29 - name: greeter_service
30 connect_timeout: 0.25s
31 type: logical_dns
32 lb_policy: round_robin
33 http2_protocol_options: {}
34 load_assignment:
35 cluster_name: greeter_service
36 endpoints:
37 - lb_endpoints:
38 - endpoint:
39 address:
40 socket_address:
41 address: host.docker.internal
42 port_value: 50051Jalankan dengan Docker:
1docker run --rm -it -v $(pwd)/envoy.yaml:/etc/envoy/envoy.yaml -p 8080:8080 envoyproxy/envoy:v1.21-latestJika backend Go ada di host, gunakan host.docker.internal di cluster Envoy.
3. Setup Frontend gRPC-Web (JavaScript/React)
A. Generate Stub JS dari .proto
Gunakan plugin protoc-gen-grpc-web :
1npm install -g protoc-gen-grpc-web
2protoc -I=proto \
3 greet.proto \
4 --js_out=import_style=commonjs:./src/pb \
5 --grpc-web_out=import_style=commonjs,mode=grpcwebtext:./src/pbDi package.json, tambahkan devDependencies:
1"dependencies": {
2 "grpc-web": "^1.4.2"
3}B. Membuat Client gRPC-Web
File: src/App.js
1import React, { useState } from 'react';
2import { GreeterClient } from './pb/greet_grpc_web_pb';
3import { HelloRequest } from './pb/greet_pb';
4
5function App() {
6 const [name, setName] = useState('');
7 const [reply, setReply] = useState('');
8
9 const client = new GreeterClient('http://localhost:8080', null, null);
10
11 const sayHello = () => {
12 const request = new HelloRequest();
13 request.setName(name);
14 client.sayHello(request, {}, (err, response) => {
15 if (err) {
16 setReply('Error: ' + err.message);
17 } else {
18 setReply(response.getMessage());
19 }
20 });
21 };
22
23 return (
24 <div>
25 <h1>gRPC-Web Demo: Say Hello</h1>
26 <input value={name} onChange={e => setName(e.target.value)} />
27 <button onClick={sayHello}>Send</button>
28 <p>Server replies: {reply}</p>
29 </div>
30 );
31}
32
33export default App;4. Simulasi Request dengan Client
Misal user mengetik “Kevin” dan submit, inilah alur request-response (disimulasikan dari network devtools):
- HTTP/1.1 POST ke
http://localhost:8080/greet.Greeter/SayHello
Content-Type: application/grpc-web-text - Envoy menerima dan forward ke grpc server Go via HTTP/2.
- Backend membalas: message
"Hello, Kevin!" - Envoy encoding response ke format grpc-web dan diantarkan ke browser.
Troubleshooting & Praktik Baik
| Masalah | Penyebab & Solusi |
|---|---|
| CORS error di JS client | Pastikan filter Envoy sudah enable cors, konfigurasi allow_origin |
Error UNAVAILABLE di client | Proxy Envoy belum terkoneksi ke backend Go, cek IP/port |
| Unexpected content-type | Pastikan client menggunakan grpcwebtext mode dan stub JS hasil generate |
| Server Go tidak handle request | Registrasi service sudah benar, lihat log server Go |
Kesimpulan
Dengan arsitektur di atas, kita dapat merasakan kekuatan gRPC di frontend browser meski ada batasan HTTP/2 dan CORS. Komunikasi antar service jadi lebih terstandarisasi, aman dari miss API, serta maintenance-lah yang lebih long-lasting!
Proses nyata mengadopsi gRPC-Web seperti ini memang sedikit “engineering-heavy” dibanding pola REST. Namun untuk aplikasi enterprise, microservices, dan real-time, investasi ini sepadan.
Semoga studi kasus ini membantu Anda menyatukan frontend JavaScript modern dengan backend Go berbasis gRPC, dan siap membangun sistem web yang scalable, modular, dan future-proof. Selamat bereksperimen!
Referensi