用 net/http 可快速启动最简 API 服务:定义 HandlerFunc 处理路由,设置 JSON Content-Type,显式写入状态码,正确解析 query 和 body,返回结构化错误,并注意 CORS 配置时机。

用 net/http 启动最简 API 服务,别急着上框架
Go 新手写第一个 API,不需要 gin、echo 或任何第三方库。标准库 net/http 足够跑通全流程,且能避开框架封装带来的黑盒感。
常见错误是直接抄一段带路由库的代码,结果连 http.ListenAndServe 崩溃都没法定位——因为没处理端口被占、TLS 配置错、或 HandlerFunc 返回值类型不对。
-
http.HandleFunc的第二个参数必须是func(http.ResponseWriter, *http.Request)类型,少一个星号(*)就编译失败 - 启动前检查端口:运行前执行
lsof -i :8080(macOS/Linux)或netstat -ano | findstr :8080(Windows) - 返回 JSON 时务必设置
w.Header().Set("Content-Type", "application/json; charset=utf-8"),否则前端可能解析成字符串
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(map[string]string{
"message": "Hello from Go",
"status": "ok",
})
}
func main() {
http.HandleFunc("/api/hello", helloHandler)
fmt.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
接收 query 参数和 JSON body,注意 r.ParseForm() 和 io.ReadAll 的区别
新手常混淆 URL 查询参数(?name=alice)和请求体(POST /api/user { "name": "alice" })。前者用 r.URL.Query().Get("name"),后者必须读取原始字节再解码。
关键陷阱:r.Body 是 io.ReadCloser,只能读一次;若先调用 r.ParseForm()(用于表单),再读 r.Body 就会得到空内容。
- GET 请求取 query:用
r.URL.Query().Get("id"),不用ParseForm - POST/PUT 的 JSON body:用
io.ReadAll(r.Body)+json.Unmarshal,读完记得defer r.Body.Close() - 如果同时需要表单字段和 JSON,优先统一走 JSON,避免混合解析逻辑
返回结构化错误,别用 http.Error 硬塞字符串
直接调 http.Error(w, "not found", http.StatusNotFound) 对调试不友好——前端拿不到错误码字段,日志也难聚合。应该统一返回 JSON 错误对象,并保持 HTTP 状态码正确。
典型翻车点:状态码设成 200 但内容是 {"error": "xxx"},导致前端以为成功;或者错误里混入敏感路径、堆栈,暴露服务细节。
- 定义错误响应结构体,含
code(业务码)、message(用户提示)、detail(仅开发看) - 用
w.WriteHeader(statusCode)显式设状态码,再写 JSON,不要依赖json.Encoder自动设 200 - 生产环境关闭详细错误(如
runtime/debug.Stack()),用日志 ID 替代堆栈
本地调试加 CORS,但上线前删掉或配白名单
前端在 localhost:3000 调用本地 Go API 时,浏览器会拦截跨域请求。开发阶段可以临时加 CORS 头,但这是权宜之计,不是解决方案。
容易忽略的是:加了 Access-Control-Allow-Origin: "*" 后,不能再带 Credentials(比如 Cookie),否则浏览器直接拒绝;而 OPTIONS 预检请求若没处理,连实际请求都发不出。
- 开发期快速通过:在 handler 开头加
w.Header().Set("Access-Control-Allow-Origin", "*") - 需要带认证时,必须指定具体域名(如
"http://localhost:3000"),并加Access-Control-Allow-Credentials: "true" - 上线前删掉这些头,改由反向代理(Nginx / Cloudflare)统一处理 CORS
真正卡住新手的,往往不是语法,而是 HTTP 协议细节没露过面:状态码语义、header 优先级、body 读取生命周期、预检请求触发条件。把这几个点摸清,比记住十个框架 API 更管用。










