本文介绍了如何使用 Go 语言构建区块链 API,特别是将底层的 JSON-RPC 接口转换成更易于前端使用的 GraphQL API。文章详细讲解了使用 JSON-RPC 的不足之处,以及 GraphQL 的优点,并提供了使用 Go 语言实现 JSON-RPC 调用和 GraphQL API 封装的具体步骤和代码示例,最后还提到了安全性和生产环境下的注意事项。

如果你曾经构建过 Web3 前端——比如 DeFi 仪表盘、钱包应用或 NFT 工具——你就会知道前端不会直接与区块链通信。你需要一个后端来处理与节点的全部“对话”。这个后端通常是某种 API 层。
现在,区块链(尤其是以太坊和 EVM 网络)默认公开一个 JSON-RPC 接口。它能用,也还行。但它也……有点烦人。它的层级非常低,所以如果你的前端需要任何稍微复杂的东西,你最终会进行一堆单独的调用,格式化奇怪的十六进制字符串,并手动将它们拼凑在一起。
这就是 GraphQL 可以提供帮助的地方。与其在三个单独的 RPC 调用中调用 eth_getBalance、eth_call 和 eth_blockNumber 仅仅是为了渲染一个页面,不如将它们包装在 GraphQL endpoint 中,一次性获得你所需要的。
本指南将逐步介绍如何使用 Go 来做到这一点。我们将从原始的 JSON-RPC 开始,然后展示如何构建一个轻量级的 GraphQL API,让你的前端生活轻松 10 倍。
如果你处理过以太坊节点,你可能已经见过 JSON-RPC。这只是一种远程告诉节点做某事的方式——比如返回地址余额、获取区块、发送交易——使用 HTTP 和一个简单的 JSON 结构。
你发送一个这样的请求:
{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0xYourAddress", "latest"],
"id": 1
}
节点会发回一个 JSON 响应。
它是裸金属。
你想显示用户的 ETH 余额和 token 余额吗?那需要两到三个不同的 RPC 调用。还想显示合约元数据吗?那就再加。你很快就会忙于处理 RPC 响应、十六进制解码和业务逻辑——仅仅是为了给前端提供一个干净的 JSON 对象。
GraphQL 颠覆了这一局面。你的前端说:“给我这种形状的这些数据,” 后端负责将它们拉到一起。
你可以:
因此,与其每次都重写相同的 RPC 粘合逻辑,不如将其一次性公开为 GraphQL API,你的前端只需请求它所需的内容即可。
以下是我们将会用到的:
gqlgen将你的架构想象成这样:
[ 前端应用 ] ⇄ GraphQL ⇄ [ Go API 服务器 ] ⇄ JSON-RPC ⇄ [ 以太坊节点 ]
你的前端永远不会直接接触区块链。一切都通过你的 API 层。

这是一个获取地址 ETH 余额的基本函数。这是一个原始的 RPC 调用——以后包装很有用。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type RPCRequest struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
ID int `json:"id"`
}
type RPCResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Result json.RawMessage `json:"result"`
Error *struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error,omitempty"`
}
func callRPC(rpcURL, method string, params []interface{}) (json.RawMessage, error) {
request := RPCRequest{
Jsonrpc: "2.0",
Method: method,
Params: params,
ID: 1,
}
payload, _ := json.Marshal(request)
resp, err := http.Post(rpcURL, "application/json", bytes.NewBuffer(payload))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var response RPCResponse
if err := json.Unmarshal(body, &response); err != nil {
return nil, err
}
if response.Error != nil {
return nil, fmt.Errorf("RPC error: %s", response.Error.Message)
}
return response.Result, nil
}
func main() {
url := "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
address := "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
result, err := callRPC(url, "eth_getBalance", []interface{}{address, "latest"})
if err != nil {
panic(err)
}
fmt.Println("Balance (in hex):", string(result))
}
这只是以十六进制(以 Wei 为单位)获取余额。你可能希望稍后将其转换为 ETH 或更好地格式化它——我们将在 GraphQL 层中执行此操作。
我们将使用 gqlgen——它有完善的文档,并且与 Go 的类型系统配合良好。
创建一个 schema.graphql 文件:
type Query {
getBalance(address: String!): String!
}
这是一个包装你的 JSON-RPC 逻辑的基本 resolver:
package graph
import (
"context"
"yourapp/rpc"
)
type queryResolver struct{}
func (r *queryResolver) GetBalance(ctx context.Context, address string) (string, error) {
result, err := rpc.Call("eth_getBalance", []interface{}{address, "latest"})
if err != nil {
return "", err
}
return string(result), nil
}
你可以稍后在此基础上构建——将余额转换为 ETH,处理边缘情况,验证地址等。
package main
import (
"log"
"net/http"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"yourapp/graph"
"yourapp/graph/generated"
)
func main() {
srv := handler.NewDefaultServer(
generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}),
)
http.Handle("/", playground.Handler("GraphQL Playground", "/query"))
http.Handle("/query", srv)
log.Println("GraphQL API running at http://localhost:8080/")
log.Fatal(http.ListenAndServe(":8080", nil))
}
就这样——你现在已经有了一个可工作的 Go 中的 GraphQL API,它可以包装一个区块链节点。
eth_sendTransaction、personal_之类的东西——这些可能很危险。想要使这个生产就绪吗?添加:
chainId 或动态切换 RPC 提供程序。如果你正在为 Web3 构建并使用 Go,这种模式——JSON-RPC 到 GraphQL——为你提供了一种将前端连接到区块链的干净方式,而无需暴露丑陋的细节。你的 UI 获得了一个不错的、结构化的 API,你的后端处理所有奇怪的 JSON 怪癖、重试和验证。
前期需要做更多的工作,但回报是值得的:更简单的前端,更少的错误,以及你可以根据需要扩展或保护的后端。
只是不要跳过安全部分。真的。
- 原文链接: medium.com/@ancilartech/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!