基于 gRPC-Web 的全栈计算器实战:Go + gRPC + Next.js 从零构建

298 阅读3分钟

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 地址

推荐阅读资料: