介绍
fiber 是受 express 启发的轻量级高性能Go语言开发框架 由go语言编写, 以 fasthttp 为核心开发而来, 至今(2026) 已经有 3个大版本 主要学习记录 v3 版本
快速开始
sh
# 创建项目目录
mkdir fiber-demo && cd fiber-demo
# 初始化模块
go mod init fiber_demo
# 添加依赖
go get github.com/gofiber/fiber/v3
# 创建 main.go 程序入口
touch main.go
# 修改 main.go, 输入后续代码后编译运行
go run main.gogo
package main
import "github.com/gofiber/fiber/v3"
func main() {
app := fiber.New()
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
}测试服务状态
打开浏览器访问 http://localhost:8080/ping 如果能够正常看到响应信息说明程序启动成功
自动重启
现在修改代码后, 必须手动停止服务, 然后再次执行 go run main.go 要想改动代码后, 立即自动重启, 就需要借助这两个工具, 达到类似 nodemon 的效果
just
alias d := dev
alias s := start
# just d 或 just dev 会执行这个 dev
# just s 或 just start 会执行 start
# 监听所有文件扩展名为go的文件内容变化后自动重新执行 `go run main.go`
dev:
watchexec -e go -r "go run main.go"
start:
go run main.go自定义配置
go
package main
import (
"fmt"
"net"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New(fiber.Config{
CaseSensitive: true, // 区分大小写
})
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000", fiber.ListenConfig{
ListenerAddrFunc: func(addr net.Addr) {
// 监听服务启动时: 输出一个日志
fmt.Println("[App]Server start on", addr)
},
})
}路由
HTTP方法
go
package main
import (
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
// 支持如下方法:
// Get(...) Router
// Head(...) Router
// Post(...) Router
// Put(...) Router
// Delete(...) Router
// Connect(...) Router
// Options(...) Router
// Trace(...) Router
// Patch(...) Router
// All(...) Router 这个比较特殊: 允许所有类型的请求方法
// 仅允许 GET 方式访问
app.Get("/get", func(c fiber.Ctx) error {
return c.SendString("from get method")
})
// 注: 与 gin 不同的是, 方法名不是全大写, 而是大驼峰式写法
app.Post("/post", func(c fiber.Ctx) error {
return c.SendString("from post method")
})
app.Listen(":3000")
}路由分组
fiber 的路由分组与 express 的路由分组类似
go
package main
import "github.com/gofiber/fiber/v3"
func main() {
app := fiber.New()
api := app.Group("/api")
v1 := api.Group("/v1", func(c fiber.Ctx) error {
// /api/v1 应用分组中间件设置响应头
c.Set("X-API-Version", "v1")
return c.Next()
})
// POST /api/v1/login
v1.Post("/login", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "from /api/v1/login",
})
})
v2 := api.Group("/v2") // /api/v2
// POST /api/v2/login
v2.Post("/login", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "from /api/v2/login",
})
})
app.Listen(":3000")
}go
package main
import "github.com/gofiber/fiber/v3"
func main() {
app := fiber.New()
app.Route("/api", func(apiRouter fiber.Router) {
apiRouter.Route("/v1", func(v1Router fiber.Router) {
v1Router.Post("/login", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "from /api/v1/login",
})
})
})
apiRouter.Route("/v2", func(v2Router fiber.Router) {
v2Router.Post("/login", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "from /api/v2/login",
})
})
})
})
app.Listen(":3000")
}获取请求信息 Request
go
package main
import (
"fmt"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
////// 1.获取路径参数
app.Get("/params/:id/:email?", func(c fiber.Ctx) error {
id := c.Params("id") // 获取路径中的 :id 参数
email := c.Params("email", "default@example.com") // 获取路径中的 :email? 可选参数, 并设置默认值
return c.JSON(fiber.Map{
"id": id,
"email": email,
})
// 测试:
// curl -i http://127.0.0.1:3000/params/1001
// curl -i http://127.0.0.1:3000/params/1002/test@example.com
})
////// 2.获取查询参数
app.Get("/search", func(c fiber.Ctx) error {
keyword := c.Query("keyword") // 获取查询关键字
page := c.Query("page", "1") // 获取查询页码
limit := c.Query("limit", "10") // 获取每页查询个数限制
return c.JSON(fiber.Map{
"keyword": keyword,
"page": page,
"limit": limit,
})
// 测试:
// curl -i http://127.0.0.1:3000/search?keyword=golang
// curl -i http://127.0.0.1:3000/search?keyword=golang&page=2
// curl -i http://127.0.0.1:3000/search?keyword=golang&page=2&limit=20
})
////// 3.获取 form 数据
app.Post("/form", func(c fiber.Ctx) error {
email := c.FormValue("email") // 获取 form 中的 name 参数
nickname := c.FormValue("nickname", "default-nickname") // 默认值
return c.JSON(fiber.Map{
"email": email,
"nickname": nickname,
})
})
////// 4.获取 json 数据
app.Post("/json", func(c fiber.Ctx) error {
// 绑定时验证数据
type LoginFormDto struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=6"`
}
// 绑定数据
loginForm := new(LoginFormDto)
if err := c.Bind().JSON(loginForm); err != nil {
return err
}
return c.JSON(fiber.Map{
"message": "login success",
"email": loginForm.Email,
"password": loginForm.Password,
})
// 测试:
// curl --request POST \
// --url http://127.0.0.1:3000/json \
// --header 'Content-Type: application/json' \
// --data '{
// "email": "test@example.com",
// "password": "123456"
// }'
})
////// 5.获取 文件上传数据
app.Post("/upload", func(c fiber.Ctx) error {
file, err := c.FormFile("file")
if err != nil {
return err
}
// 文件信息
fmt.Println("文件名:", file.Filename)
fmt.Println("文件大小:", file.Size)
fmt.Println("文件类型:", file.Header.Get("Content-Type"))
// 保存到本地(注:uploads目录必须存在, 否则报错)
err = c.SaveFile(file, fmt.Sprintf("./uploads/%s", file.Filename))
if err != nil {
return err
}
return c.JSON(fiber.Map{
"message": "上传成功",
"filepath": "./uploads/" + file.Filename,
"filename": file.Filename,
"size": file.Size,
})
})
////// 6.获取请求头
app.Get("/headers", func(c fiber.Ctx) error {
// 获取所有请求头字段
headers := c.GetReqHeaders()
// 获取单个字段
accessToken := c.Get("Authorization")
return c.JSON(fiber.Map{
"headers": headers,
"accessToken": accessToken,
})
})
////// 7.获取 Request 实例
app.Get("/request", func(c fiber.Ctx) error {
fmt.Println("[Request]", c.Request())
// [Request] GET /request HTTP/1.1
// User-Agent: HTTPie
// Host: 127.0.0.1:3000
// Connection: close
fmt.Println("[Request]", c.Request().URI())
// [Request] http://127.0.0.1:3000/request
return c.JSON(fiber.Map{
"message": "ok",
})
})
app.Listen(":3000")
}设置响应信息 Response
go
package main
import (
"net/http"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
////// 1.响应字符串纯文本
app.Get("/str", func(c fiber.Ctx) error {
return c.SendString("hello world")
})
////// 2.响应JSON: 自动设置 Content-Type: application/json
app.Get("/json", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "hello world",
})
})
////// 3.响应HTML: 手动设置 Content-Type: text/html
app.Get("/html", func(c fiber.Ctx) error {
c.Set("Content-Type", "text/html")
return c.SendString("<h1>hello world</h1>")
})
////// 4.响应文件下载
app.Get("/download", func(c fiber.Ctx) error {
// c.Attachment("./uploads/avatar.png")
// return nil
// 或者直接试用 Download 方法
// 会自动设置响应头, 唤起浏览器下载
// Content-Disposition: attachment; filename="文件.txt"; filename*=UTF-8''%E6%96%87%E4%BB%B6.txt
return c.Download("./uploads/avatar.png")
})
////// 5.响应一个文件内容(非下载,而是在浏览器中打开)
app.Get("/file", func(c fiber.Ctx) error {
return c.SendFile("./uploads/avatar.png")
})
////// 6.响应重定向(临时/永久)
app.Get("/redirect", func(c fiber.Ctx) error {
// return c.Redirect().Status(http.StatusTemporaryRedirect).To("https://qq.com")
return c.Redirect().Status(http.StatusPermanentRedirect).To("https://qq.com")
})
app.Listen(":3000")
}数据绑定与验证
与 gin 非常类似, 可以直接将 HTTP 请求的内容解析绑定为 go 语言的 struct
而数据验证方面, 二者都是用的 validator 这个库, 所以验证的用法几乎一模一样
go
package main
import (
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
////// 0.绑定 params 为 golang 的 struct
app.Get("/user/:id/:name?", func(c fiber.Ctx) error {
type User struct {
ID int `uri:"id" validate:"required"`
Name string `uri:"name,default=test" validate:"required"`
}
user := new(User)
if err := c.Bind().URI(user); err != nil {
return err
}
return c.JSON(user)
})
////// 1.绑定 query 为 golang 的 struct
app.Get("/search", func(c fiber.Ctx) error {
type SearchQuery struct {
Keyword string `query:"keyword" validate:"required"`
Page int `query:"page,default=1"`
Limit int `query:"page,default=10"`
}
query := new(SearchQuery)
if err := c.Bind().Query(query); err != nil {
return err
}
return c.JSON(query)
})
// 绑定json/绑定form/绑定request-header 等例子请直接查看文档
app.Listen(":3000")
}中间件
中间件分类
- 局部中间件: 作用于单个路由
- 分组中间件: 作用于某个路由分组
- 全局中间件: 作用于整个服务的所有路由
go
package main
import (
"fmt"
"github.com/gofiber/fiber/v3"
)
func SetApiVersionHeaser(c fiber.Ctx) error {
// 局部中间件
fmt.Println("[SetApiVersionHeaser] Executing")
c.Set("X-API-Version", "v1")
return c.Next()
}
func main() {
app := fiber.New()
app.Use(func(c fiber.Ctx) error {
// 全局中间件
fmt.Println("[GlobalMiddleware] Executing")
return c.Next()
})
apiRouter := app.Group("/api", func(c fiber.Ctx) error {
// 路由分组中间件
fmt.Println("[APIRouter] Executing")
return c.Next()
})
apiRouter.Get("/v1/hello", SetApiVersionHeaser, func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "Hello World",
})
})
// 控制台查看输出的调试信息
// 测试: curl -i http://127.0.0.1:3000/api/v1/hello
app.Listen(":3000")
}CORS 跨域中间件
允许跨域中间件
go
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/cors"
)
func main() {
app := fiber.New()
app.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-API-VERSION"},
AllowMethods: []string{
fiber.MethodGet,
fiber.MethodPost,
fiber.MethodHead,
fiber.MethodPut,
fiber.MethodDelete,
fiber.MethodPatch,
},
}))
app.Get("/hello", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "Hello World",
})
})
// 控制台查看输出的调试信息
// 测试(注意,需要设置一些参数,否则无法触发跨域安全规则):
// curl -i -X OPTIONS \
// -H "Origin: http://example.com" \
// -H "Access-Control-Request-Method: POST" \
// http://127.0.0.1:3000/hello
app.Listen(":3000")
}静态文件服务中间件
go
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/static"
)
func main() {
app := fiber.New()
// 提供单个文件
app.Use("/favicon.png", static.New("./uploads/favicon.png"))
// 提供整个目录
app.Use("/uploads/*", static.New("./uploads"))
// 请确保目录和文件存在, 才能正确访问到
app.Listen(":3000")
}实用技巧
自定义全局错误处理
go
package main
import (
"github.com/gofiber/fiber/v3"
recoverer "github.com/gofiber/fiber/v3/middleware/recover"
)
func main() {
app := fiber.New(fiber.Config{
ErrorHandler: func(ctx fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
message := err.Error()
// 统一返回 json
return ctx.Status(code).JSON(fiber.Map{
"code": code,
"message": message,
})
},
})
// 这个中间件, 可以让程序 panic 后, 主程序不崩溃
// 而是捕获错误, 统一交给 fiber.Conifg#ErrorHandler 处理
app.Use(recoverer.New())
app.Get("/error", func(c fiber.Ctx) error {
// 这个文件不存在就会报错
return c.SendFile("file-not-exists")
})
app.Get("/panic", func(c fiber.Ctx) error {
// 手动让程序恐慌
panic("custom panic")
})
app.Listen(":3000")
}格式化响应体 JSON
- 统一格式化响应体的 json 格式
go
package main
import (
"github.com/gofiber/fiber/v3"
recoverer "github.com/gofiber/fiber/v3/middleware/recover"
)
// 统一返回 json 的格式
type ResponseFmt struct {
Errno int `json:"errno"`
Message string `json:"message"`
Data any `json:"data"`
}
// 直接作为工具函数使用
func Success(c fiber.Ctx, data any) error {
return c.JSON(ResponseFmt{
Errno: 0,
Message: "success",
Data: data,
})
}
func Failed(c fiber.Ctx, errno int, messages ...string) error {
msg := "failed"
if len(messages) > 0 {
msg = messages[0]
}
return c.JSON(ResponseFmt{
Errno: errno,
Message: msg,
Data: nil,
})
}
func Error(c fiber.Ctx, errno int, message string) error {
return c.JSON(ResponseFmt{
Errno: errno,
Message: message,
Data: nil,
})
}
func main() {
app := fiber.New(fiber.Config{
// 报错/panic也统一返回 json
ErrorHandler: func(ctx fiber.Ctx, err error) error {
status := fiber.StatusInternalServerError
message := err.Error()
ctx.Status(status)
return Error(ctx, status, message)
},
})
app.Use(recoverer.New())
// 1.成功的响应
app.Get("/success", func(c fiber.Ctx) error {
return Success(c, fiber.Map{
"count": 100,
"items": []string{
"item1",
"item2",
"item3",
"item4",
},
})
})
// 2.失败响应
app.Get("/failed", func(c fiber.Ctx) error {
// return Failed(c, 1401)
return Failed(c, 1401, "please login first")
})
// 3.程序错误响应
app.Get("/error", func(c fiber.Ctx) error {
return c.SendFile("file-not-exists")
})
// 4.程序恐慌响应
app.Get("/panic", func(c fiber.Ctx) error {
panic("custom panic message")
})
app.Listen(":3000")
}登录认证中间件及登录接口设计
go
package main
import (
"fmt"
"strings"
"github.com/gofiber/fiber/v3"
recoverer "github.com/gofiber/fiber/v3/middleware/recover"
)
func Auth(c fiber.Ctx) error {
// 验证 header
authHeader := c.Get("Authorization")
if authHeader == "" {
return fiber.NewError(fiber.StatusUnauthorized, "please login first")
}
// 验证 jwt 是否正确 authorization => Bearer <token>
accessToken := strings.TrimPrefix(authHeader, "Bearer ")
fmt.Println("[Auth]accessToken:", accessToken)
uid, email, err := VerifyAccessToken(accessToken)
if err != nil {
return fiber.NewError(fiber.StatusUnauthorized, err.Error())
}
c.Set("uid", string(uid))
c.Set("email", email)
return c.Next()
}
func main() {
app := fiber.New(fiber.Config{
// 报错/panic也统一返回 json
ErrorHandler: func(c fiber.Ctx, err error) error {
status := fiber.StatusInternalServerError
message := err.Error()
c.Status(status)
return c.JSON(fiber.Map{
"status": status,
"message": message,
})
},
})
app.Use(recoverer.New())
// 1.登录接口
app.Post("/login", func(c fiber.Ctx) error {
// 获取 & 绑定 & 验证 请求参数
type LoginFormDto struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=6"`
}
loginForm := new(LoginFormDto)
if err := c.Bind().JSON(loginForm); err != nil {
return err
}
// 模拟数据库查询
expectedEmail := "admin@example.com"
if loginForm.Email != expectedEmail || loginForm.Password != "123456" {
return fiber.NewError(fiber.StatusUnauthorized, "用户名或密码错误")
}
// 签发访问令牌 jwt
const userId = 1001
accessToken, err := GenAccessToken(userId, expectedEmail)
if err != nil {
return err
}
refreshToken, err := GenRefreshToken(userId, expectedEmail)
if err != nil {
return err
}
return c.JSON(fiber.Map{
"email": loginForm.Email,
"accessToken": accessToken,
"refreshToken": refreshToken,
})
})
// 2.程序错误响应
app.Get("/refresh_access_token", func(c fiber.Ctx) error {
refreshToken := c.Query("refresh_token")
if refreshToken == "" {
return fiber.NewError(fiber.StatusBadRequest, "refresh_token 不能为空")
}
// 刷新 accessToken & refreshToken
newAccessToken, newRefreshToken, err := RenewAccessToken(refreshToken)
if err != nil {
return err
}
return c.JSON(fiber.Map{
"accessToken": newAccessToken,
"refreshToken": newRefreshToken,
})
})
// 3.受保护的接口
app.Get("/profile", Auth, func(c fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "form profile, protected api",
})
})
app.Listen(":3000")
}go
package main
import (
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
// Token 过期时间配置
const (
AccessTokenExpire = time.Minute * 1 // AccessToken 1分钟
RefreshTokenExpire = time.Hour * 24 * 7 // RefreshToken 7天
// 实际项目中应该从环境变量读取
AccessTokenSecret = "access-token-secret-key-here"
RefreshTokenSecret = "refresh-token-secret-key-here"
)
// Issuer 签发者
const Issuer = "app-name-here"
// TokenType Token 类型标识
type TokenType string
const (
TokenTypeAccess TokenType = "access"
TokenTypeRefresh TokenType = "refresh"
)
// CustomClaims 自定义 JWT Claims
type CustomClaims struct {
UserID int `json:"user_id"`
Email string `json:"email"`
TokenType TokenType `json:"token_type"`
jwt.RegisteredClaims
}
// genAccessToken 生成访问令牌(短期有效)
func GenAccessToken(userId int, email string) (string, error) {
claims := CustomClaims{
UserID: userId,
Email: email,
TokenType: TokenTypeAccess,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(AccessTokenExpire)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: Issuer,
Subject: fmt.Sprintf("%d", userId),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(AccessTokenSecret))
}
// verifyAccessToken 验证访问令牌
func VerifyAccessToken(accessToken string) (int, string, error) {
token, err := jwt.ParseWithClaims(accessToken, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
// 验证签名算法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(AccessTokenSecret), nil
})
if err != nil {
// 区分错误类型
if errors.Is(err, jwt.ErrTokenExpired) {
return 0, "", errors.New("token已过期")
}
if errors.Is(err, jwt.ErrTokenMalformed) {
return 0, "", errors.New("token格式错误")
}
return 0, "", errors.New("token验证失败")
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
// 验证 Token 类型
if claims.TokenType != TokenTypeAccess {
return 0, "", errors.New("token类型错误,不是access token")
}
return claims.UserID, claims.Email, nil
}
return 0, "", errors.New("无效的token")
}
// genRefreshToken 生成刷新令牌(长期有效)
func GenRefreshToken(userId int, email string) (string, error) {
claims := CustomClaims{
UserID: userId,
Email: email,
TokenType: TokenTypeRefresh,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(RefreshTokenExpire)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: Issuer,
Subject: fmt.Sprintf("%d", userId),
ID: generateTokenID(), // 可选:用于黑名单机制
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(RefreshTokenSecret))
}
// verifyRefreshToken 验证刷新令牌
func VerifyRefreshToken(refreshToken string) (int, string, error) {
token, err := jwt.ParseWithClaims(refreshToken, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(RefreshTokenSecret), nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return 0, "", errors.New("refresh token已过期,请重新登录")
}
return 0, "", errors.New("refresh token验证失败")
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
if claims.TokenType != TokenTypeRefresh {
return 0, "", errors.New("token类型错误,不是refresh token")
}
return claims.UserID, claims.Email, nil
}
return 0, "", errors.New("无效的refresh token")
}
// generateTokenID 生成唯一Token ID(用于黑名单或刷新 token 轮换机制)
func generateTokenID() string {
return uuid.New().String()
}
// RenewAccessToken 使用 Refresh Token 换取新的 Access Token
func RenewAccessToken(refreshToken string) (newAccessToken string, newRefreshToken string, err error) {
userId, email, err := VerifyRefreshToken(refreshToken)
if err != nil {
return "", "", err
}
// 生成新的双 Token(Token 轮换机制,可选)
accessToken, err := GenAccessToken(userId, email)
if err != nil {
return "", "", err
}
// 可选:同时刷新 refresh token(增强安全性)
newRefresh, err := GenRefreshToken(userId, email)
if err != nil {
return "", "", err
}
return accessToken, newRefresh, nil
}http
### kulala-config
@host = http://localhost:3000
### LOGIN_REQ
POST {{host}}/login HTTP/1.1
Content-Type: application/json
{
"email": "admin@example.com",
"password": "123456"
}
### refresh-access-token
GET {{host}}/refresh_access_token?refresh_token={{LOGIN_REQ.response.body.$.refreshToken}} HTTP/1.1
### profile
GET {{host}}/profile HTTP/1.1
Authorization: Bearer {{LOGIN_REQ.response.body.$.accessToken}}