Go + gRPC-Web + Next.js 全栈实战:构建一个简易计算器
本文将带你通过一个完整的“计算器”全栈项目,从零体验如何使用 Go + gRPC + gRPC-Web + Next.js 构建现代 Web 应用,掌握前后端高效通信的实战技能。
1. 为什么写这个项目?
在 Web 项目中,传统 REST API 一直是最主流的前后端通信方式。但随着系统复杂度提升,它逐渐暴露出一些问题,比如接口定义不统一、缺乏强类型支持、维护成本高等。
为了追求更高的开发效率和更清晰的通信契约,我开始尝试 gRPC + gRPC-Web 来替代传统的 HTTP API,结合 Protocol Buffers 强类型定义,实现更稳定、更规范的前后端交互。
因此,我动手实现了一个“小而美”的全栈计算器项目:
-
后端使用 Go + gRPC,提供加减乘除的服务;
-
前端使用 Next.js,构建用户界面;
-
前后端通信采用 gRPC-Web 协议;
-
数据结构使用 protobuf 协议统一定义。
2. 技术栈一览
层级
技术栈
用途
前端
Next.js + TypeScript
构建用户界面
通信
gRPC-Web + Protobuf
前后端通信协议
后端
Go + gRPC
实现加减乘除服务
代理层
grpc-web proxy(connect-go)
转发 gRPC-Web 请求
工具链
protoc、buf、connect 相关插件
生成代码、协议定义支持
3. 项目依赖清单
前端依赖
npm install @connectrpc/connect-web @connectrpc/connect-query @connectrpc/connect
npm install protobufjs
后端依赖
go get github.com/bufbuild/connect-go
go get google.golang.org/protobuf
go get github.com/rs/cors
工具链依赖
go install github.com/bufbuild/buf/cmd/buf@latest
go install github.com/bufbuild/connect-go/cmd/protoc-gen-connect-go@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
4. 项目结构
后端结构
calculator-backend
├── calculator # proto 文件
├── github.com # 生成的 go 文件路径
├── calculator_test.go # 单元测试
├── go.mod # 模块声明
└── main.go # 主入口文件
前端结构
calculator-frontend
├── app
│ ├── generated
│ │ └── calculator # 生成的前端代码
│ ├── globals.css
│ └── page.tsx # 主页面
├── calculator # proto 文件
└── public
└── ... # 配置及静态资源
5. Protobuf 协议定义
syntax = "proto3";
package calculator;
option go_package = "github.com/2760439882/calculator-backend/calculator;calculator";
service Calculator {
rpc Calculate(CalculationRequest) returns (CalculationResponse);
}
message CalculationRequest {
double operand1 = 1;
double operand2 = 2;
string operator = 3;
}
message CalculationResponse {
double result = 1;
}
使用命令生成代码:
buf generate
6. 前端调用方式(gRPC-Web)
安装依赖
npm install google-protobuf grpc-web
创建请求
const client = new CalculatorClient('http://localhost:8080', null, null);
const req = new CalculateRequest();
req.setOperand1(parseFloat(operand1));
req.setOperand2(parseFloat(operand2));
req.setOperator(operator);
client.calculate(req, {}, (err, response) => {
if (err) {
setError('请求失败: ' + err.message);
return;
}
const res = response.getResult();
setResult(res);
});
7. 跨域问题与解决方案
前端运行在 localhost:3000,后端运行在 localhost:8080,两者不同源会触发 CORS 跨域问题。
后端解决方式:引入 cors 中间件
httpServer := http.Server{
Addr: ":8080",
Handler: cors.New(cors.Options{
AllowedOrigins: []string{"http://localhost:3000"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "X-Grpc-Web", "X-User-Agent", "grpc-timeout"},
AllowCredentials: true,
}).Handler(你的Handler),
}
也可以前端配置代理:
async rewrites() {
return [
{
source: "/api/:path*",
destination: "http://localhost:8080/:path*",
},
];
}
配置了后端 CORS 后,前端可以直接连接,不必配置代理。
8. 踩坑总结
🧱 1. 跨域问题
一开始没有配置好 CORS,前端请求总是红色报错。加上 github.com/rs/cors 并正确设置 headers 后正常。
📂 2. proto 文件生成路径错乱
忘记加 --go_opt=paths=source_relative,或者没有配置好 go_package,导致生成的文件包名错乱,引用失败。
🔌 3. gRPC 请求路径不匹配
Connect 默认暴露路径是:
/calculator.Calculator/Calculate
如果前端写成 /Calculate,会直接失败。
建议:
-
proto 中保持 package + service 一致性
-
使用 connect 协议统一生成器
-
开启 connect 调试日志
实际运行效果
前端运行效果
后端运行效果
跨域请求报错
结语
相比传统 REST,gRPC-Web 的确提升了开发体验和通信效率。但它也有学习成本,比如 proto 生成流程、路径配置、跨域处理等。希望通过这个小项目,能帮助你快速上手 gRPC 全栈开发,避开一些常见的坑。
欢迎 Star 项目 👉 GitHub 地址