Building REST APIs with Golang Fiber
A comprehensive guide to building high-performance REST APIs using the Fiber web framework in Go.
A comprehensive guide to building high-performance REST APIs using the Fiber web framework in Go.

Introduction: In the modern web development landscape, speed and efficiency are paramount. While Go is already known for its excellent performance, choosing the right web framework can make a significant difference in your API's responsiveness and scalability. Enter Fiber—a web framework that combines the familiar syntax of Express.js with the raw speed of Go's Fasthttp engine. Whether you're building microservices, RESTful APIs, or high-throughput web applications, Fiber provides the tools and performance you need to deliver exceptional results.
Fiber is an Express-inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. It's designed to ease things up for fast development with zero memory allocation and performance in mind. With its intuitive API, extensive middleware ecosystem, and impressive benchmarks, Fiber has become one of the most popular choices for building web applications in Go.
Before we dive into code, let's understand what makes Fiber an excellent choice for building REST APIs.
Fiber is built on top of Fasthttp, which is significantly faster than Go's standard net/http package. In benchmarks, Fiber consistently outperforms other Go web frameworks, handling over 6 million requests per second on a single machine. This performance makes it ideal for high-traffic applications and microservices.
If you've worked with Express.js in Node.js, Fiber will feel immediately familiar. This familiarity reduces the learning curve and makes it easy to translate concepts between ecosystems. You get the developer experience of Express with the performance of Go.
Fiber is designed to minimize memory allocations, reducing garbage collection overhead and improving overall performance. This design philosophy makes it particularly well-suited for building efficient, scalable services.
Fiber comes with a comprehensive set of built-in middleware for common tasks like CORS, logging, compression, rate limiting, and more. The middleware system is easy to use and extend, allowing you to build complex functionality with minimal code.
Fiber provides a powerful routing system with support for route parameters, query strings, wildcards, and route grouping. This makes it easy to organize your API endpoints logically and maintain clean code.
With over 30,000 stars on GitHub and an active community, Fiber has excellent documentation, numerous examples, and a wealth of community-contributed middleware and extensions.
Let's set up a new Go project with Fiber.
Ensure you have Go installed (version 1.17 or higher):
go version
# Create project directory
mkdir fiber-api
cd fiber-api
# Initialize Go module
go mod init github.com/yourusername/fiber-api
# Install Fiber
go get -u github.com/gofiber/fiber/v2
For a well-organized API, use this structure:
fiber-api/
├── main.go
├── go.mod
├── go.sum
├── handlers/
│ ├── user.go
│ └── auth.go
├── models/
│ └── user.go
├── middleware/
│ └── auth.go
├── database/
│ └── connection.go
├── routes/
│ └── routes.go
└── utils/
└── helpers.go
Let's create a simple "Hello World" application to understand Fiber's basics.
Create main.go:
package main
import (
"log"
"github.com/gofiber/fiber/v2"
)
func main() {
// Create a new Fiber instance
app := fiber.New()
// Define a route
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
// Start server on port 3000
log.Fatal(app.Listen(":3000"))
}
Run the application:
go run main.go
Visit http://localhost:3000 to see your API in action.
Fiber allows extensive configuration:
app := fiber.New(fiber.Config{
ServerHeader: "Fiber",
AppName: "My API v1.0.0",
Prefork: false,
StrictRouting: false,
CaseSensitive: false,
ErrorHandler: customErrorHandler,
ReadTimeout: time.Second * 10,
WriteTimeout: time.Second * 10,
})
Implement graceful shutdown to handle ongoing requests:
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
// Channel to listen for interrupt signals
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
// Start server in a goroutine
go func() {
if err := app.Listen(":3000"); err != nil {
log.Panic(err)
}
}()
// Wait for interrupt signal
<-c
log.Println("Gracefully shutting down...")
_ = app.Shutdown()
log.Println("Server shutdown complete")
}
Fiber provides a powerful and intuitive routing system.
// GET request
app.Get("/users", getUsers)
// POST request
app.Post("/users", createUser)
// PUT request
app.Put("/users/:id", updateUser)
// DELETE request
app.Delete("/users/:id", deleteUser)
// PATCH request
app.Patch("/users/:id", patchUser)
// Handle all methods
app.All("/api", func(c *fiber.Ctx) error {
return c.SendString("Any HTTP method")
})
// Single parameter
app.Get("/users/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
return c.SendString("User ID: " + id)
})
// Multiple parameters
app.Get("/users/:id/posts/:postId", func(c *fiber.Ctx) error {
userID := c.Params("id")
postID := c.Params("postId")
return c.JSON(fiber.Map{
"userID": userID,
"postID": postID,
})
})
// Optional parameters
app.Get("/users/:id?", func(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
return c.SendString("All users")
}
return c.SendString("User ID: " + id)
})
// Match anything after /api/
app.Get("/api/*", func(c *fiber.Ctx) error {
path := c.Params("*")
return c.SendString("Path: " + path)
})
// File serving
app.Get("/files/*", func(c *fiber.Ctx) error {
return c.SendFile("./public/" + c.Params("*"))
})
Organize related routes together:
func setupRoutes(app *fiber.App) {
// API v1 group
v1 := app.Group("/api/v1")
// Users routes
users := v1.Group("/users")
users.Get("/", getUsers)
users.Get("/:id", getUser)
users.Post("/", createUser)
users.Put("/:id", updateUser)
users.Delete("/:id", deleteUser)
// Posts routes
posts := v1.Group("/posts")
posts.Get("/", getPosts)
posts.Post("/", createPost)
// API v2 group
v2 := app.Group("/api/v2")
v2.Get("/users", getUsersV2)
}
// Named routes
app.Get("/users/:id", getUser).Name("user")
// Generate URL from route name
url, err := app.GetRoute("user").BuildPath(map[string]string{
"id": "123",
})
// url: "/users/123"
Fiber provides comprehensive methods to access request data.
app.Get("/search", func(c *fiber.Ctx) error {
// Single query parameter
query := c.Query("q")
// Query with default value
page := c.Query("page", "1")
// All query parameters
queries := c.Queries()
return c.JSON(fiber.Map{
"query": query,
"page": page,
"all": queries,
})
})
// GET /search?q=fiber&page=2&sort=desc
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
app.Post("/users", func(c *fiber.Ctx) error {
user := new(User)
// Parse JSON body
if err := c.BodyParser(user); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request body",
})
}
return c.JSON(user)
})
app.Get("/headers", func(c *fiber.Ctx) error {
// Get single header
auth := c.Get("Authorization")
// Get header with default
contentType := c.Get("Content-Type", "application/json")
// Get all headers
headers := c.GetReqHeaders()
return c.JSON(fiber.Map{
"authorization": auth,
"contentType": contentType,
"all": headers,
})
})
app.Get("/cookie", func(c *fiber.Ctx) error {
// Get cookie
value := c.Cookies("name")
// Get cookie with default
session := c.Cookies("session", "default-session")
return c.SendString("Cookie value: " + value)
})
app.Post("/cookie", func(c *fiber.Ctx) error {
// Set cookie
c.Cookie(&fiber.Cookie{
Name: "session",
Value: "session-value",
Expires: time.Now().Add(24 * time.Hour),
HTTPOnly: true,
Secure: true,
})
return c.SendString("Cookie set")
})
app.Post("/form", func(c *fiber.Ctx) error {
// Get form value
name := c.FormValue("name")
// Get form value with default
email := c.FormValue("email", "default@example.com")
// Get all form values
data := c.Request().PostArgs()
return c.JSON(fiber.Map{
"name": name,
"email": email,
})
})
Fiber provides multiple ways to send responses.
app.Get("/text", func(c *fiber.Ctx) error {
return c.SendString("Plain text response")
})
app.Get("/json", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"message": "Success",
"data": fiber.Map{
"id": 1,
"name": "John Doe",
},
})
})
// With struct
type Response struct {
Message string `json:"message"`
Data User `json:"data"`
}
app.Get("/user", func(c *fiber.Ctx) error {
return c.JSON(Response{
Message: "Success",
Data: User{Name: "John", Email: "john@example.com"},
})
})
// Set status and send
app.Get("/created", func(c *fiber.Ctx) error {
return c.Status(201).JSON(fiber.Map{
"message": "Created",
})
})
// Common status codes
c.Status(fiber.StatusOK) // 200
c.Status(fiber.StatusCreated) // 201
c.Status(fiber.StatusBadRequest) // 400
c.Status(fiber.StatusUnauthorized) // 401
c.Status(fiber.StatusNotFound) // 404
c.Status(fiber.StatusInternalServerError) // 500
app.Get("/old", func(c *fiber.Ctx) error {
return c.Redirect("/new")
})
// Redirect with status code
app.Get("/moved", func(c *fiber.Ctx) error {
return c.Redirect("/new-location", 301)
})
app.Get("/download", func(c *fiber.Ctx) error {
return c.Download("./files/document.pdf")
})
app.Get("/file", func(c *fiber.Ctx) error {
return c.SendFile("./public/image.png")
})
app.Get("/headers", func(c *fiber.Ctx) error {
c.Set("X-Custom-Header", "value")
c.Set("Cache-Control", "no-cache")
return c.SendString("Headers set")
})
Middleware functions execute before or after route handlers, enabling powerful functionality.
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/recover"
)
func main() {
app := fiber.New()
// Logger middleware
app.Use(logger.New(logger.Config{
Format: "[${time}] ${status} - ${method} ${path}\n",
}))
// CORS middleware
app.Use(cors.New(cors.Config{
AllowOrigins: "https://example.com",
AllowHeaders: "Origin, Content-Type, Accept",
}))
// Compress middleware
app.Use(compress.New())
// Recover middleware (panic recovery)
app.Use(recover.New())
// Routes...
}
// Simple middleware
func authMiddleware(c *fiber.Ctx) error {
token := c.Get("Authorization")
if token == "" {
return c.Status(401).JSON(fiber.Map{
"error": "Unauthorized",
})
}
// Continue to next handler
return c.Next()
}
// Use middleware
app.Use(authMiddleware)
// Or for specific routes
app.Get("/protected", authMiddleware, protectedHandler)
func userMiddleware(c *fiber.Ctx) error {
// Get user from database or token
user := getUserFromToken(c.Get("Authorization"))
// Store in context
c.Locals("user", user)
return c.Next()
}
func protectedHandler(c *fiber.Ctx) error {
// Retrieve from context
user := c.Locals("user").(User)
return c.JSON(fiber.Map{
"user": user,
})
}
// Apply to specific routes
app.Get("/admin", adminMiddleware, adminHandler)
// Apply to route group
admin := app.Group("/admin", adminMiddleware)
admin.Get("/users", getUsers)
admin.Get("/settings", getSettings)
Let's build a complete CRUD API for managing users.
// models/user.go
package models
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
CreatedAt string `json:"created_at"`
}
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"required,min=18,max=120"`
}
type UpdateUserRequest struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Age int `json:"age,omitempty"`
}
// handlers/user.go
package handlers
import (
"github.com/gofiber/fiber/v2"
"github.com/yourusername/fiber-api/models"
)
// In-memory storage (use database in production)
var users = []models.User{
{ID: 1, Name: "John Doe", Email: "john@example.com", Age: 30},
{ID: 2, Name: "Jane Smith", Email: "jane@example.com", Age: 25},
}
var nextID = 3
// GetUsers retrieves all users
func GetUsers(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"success": true,
"data": users,
})
}
// GetUser retrieves a single user by ID
func GetUser(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"success": false,
"error": "Invalid user ID",
})
}
for _, user := range users {
if user.ID == id {
return c.JSON(fiber.Map{
"success": true,
"data": user,
})
}
}
return c.Status(404).JSON(fiber.Map{
"success": false,
"error": "User not found",
})
}
// CreateUser creates a new user
func CreateUser(c *fiber.Ctx) error {
req := new(models.CreateUserRequest)
if err := c.BodyParser(req); err != nil {
return c.Status(400).JSON(fiber.Map{
"success": false,
"error": "Invalid request body",
})
}
// Validate request (implement validation)
if req.Name == "" || req.Email == "" {
return c.Status(400).JSON(fiber.Map{
"success": false,
"error": "Name and email are required",
})
}
user := models.User{
ID: nextID,
Name: req.Name,
Email: req.Email,
Age: req.Age,
}
nextID++
users = append(users, user)
return c.Status(201).JSON(fiber.Map{
"success": true,
"data": user,
})
}
// UpdateUser updates an existing user
func UpdateUser(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"success": false,
"error": "Invalid user ID",
})
}
req := new(models.UpdateUserRequest)
if err := c.BodyParser(req); err != nil {
return c.Status(400).JSON(fiber.Map{
"success": false,
"error": "Invalid request body",
})
}
for i, user := range users {
if user.ID == id {
if req.Name != "" {
users[i].Name = req.Name
}
if req.Email != "" {
users[i].Email = req.Email
}
if req.Age != 0 {
users[i].Age = req.Age
}
return c.JSON(fiber.Map{
"success": true,
"data": users[i],
})
}
}
return c.Status(404).JSON(fiber.Map{
"success": false,
"error": "User not found",
})
}
// DeleteUser deletes a user
func DeleteUser(c *fiber.Ctx) error {
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"success": false,
"error": "Invalid user ID",
})
}
for i, user := range users {
if user.ID == id {
users = append(users[:i], users[i+1:]...)
return c.JSON(fiber.Map{
"success": true,
"message": "User deleted successfully",
})
}
}
return c.Status(404).JSON(fiber.Map{
"success": false,
"error": "User not found",
})
}
// routes/routes.go
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/yourusername/fiber-api/handlers"
)
func Setup(app *fiber.App) {
api := app.Group("/api/v1")
// User routes
users := api.Group("/users")
users.Get("/", handlers.GetUsers)
users.Get("/:id", handlers.GetUser)
users.Post("/", handlers.CreateUser)
users.Put("/:id", handlers.UpdateUser)
users.Delete("/:id", handlers.DeleteUser)
}
// main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/yourusername/fiber-api/routes"
)
func main() {
app := fiber.New(fiber.Config{
AppName: "User API v1.0.0",
})
// Middleware
app.Use(logger.New())
app.Use(cors.New())
// Routes
routes.Setup(app)
// Start server
log.Fatal(app.Listen(":3000"))
}
Proper validation is crucial for API security and data integrity.
go get github.com/go-playground/validator/v10
import "github.com/go-playground/validator/v10"
var validate = validator.New()
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"required,min=18,max=120"`
Password string `json:"password" validate:"required,min=8"`
}
func CreateUser(c *fiber.Ctx) error {
req := new(CreateUserRequest)
if err := c.BodyParser(req); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request body",
})
}
// Validate
if err := validate.Struct(req); err != nil {
errors := err.(validator.ValidationErrors)
return c.Status(400).JSON(fiber.Map{
"error": "Validation failed",
"details": errors.Error(),
})
}
// Process valid request...
return c.Status(201).JSON(fiber.Map{
"message": "User created",
})
}
func formatValidationErrors(err error) map[string]string {
errors := make(map[string]string)
for _, err := range err.(validator.ValidationErrors) {
field := err.Field()
switch err.Tag() {
case "required":
errors[field] = field + " is required"
case "email":
errors[field] = "Invalid email format"
case "min":
errors[field] = field + " must be at least " + err.Param()
case "max":
errors[field] = field + " must be at most " + err.Param()
default:
errors[field] = "Invalid " + field
}
}
return errors
}
// Usage in handler
if err := validate.Struct(req); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Validation failed",
"details": formatValidationErrors(err),
})
}
Let's integrate a database using GORM.
go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
// database/connection.go
package database
import (
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var DB *gorm.DB
func Connect() {
dsn := "host=localhost user=postgres password=postgres dbname=fiberdb port=5432 sslmode=disable"
var err error
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
log.Println("Database connected successfully")
}
func Migrate(models ...interface{}) {
err := DB.AutoMigrate(models...)
if err != nil {
log.Fatal("Migration failed:", err)
}
log.Println("Database migration completed")
}
// models/user.go
package models
import "gorm.io/gorm"
type User struct {
gorm.Model
Name string `json:"name" gorm:"not null"`
Email string `json:"email" gorm:"unique;not null"`
Age int `json:"age"`
Password string `json:"-" gorm:"not null"`
}
// handlers/user.go
package handlers
import (
"github.com/gofiber/fiber/v2"
"github.com/yourusername/fiber-api/database"
"github.com/yourusername/fiber-api/models"
)
func GetUsers(c *fiber.Ctx) error {
var users []models.User
result := database.DB.Find(&users)
if result.Error != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to fetch users",
})
}
return c.JSON(fiber.Map{
"success": true,
"data": users,
})
}
func GetUser(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
result := database.DB.First(&user, id)
if result.Error != nil {
return c.Status(404).JSON(fiber.Map{
"error": "User not found",
})
}
return c.JSON(fiber.Map{
"success": true,
"data": user,
})
}
func CreateUser(c *fiber.Ctx) error {
user := new(models.User)
if err := c.BodyParser(user); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request body",
})
}
result := database.DB.Create(user)
if result.Error != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to create user",
})
}
return c.Status(201).JSON(fiber.Map{
"success": true,
"data": user,
})
}
func UpdateUser(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
return c.Status(404).JSON(fiber.Map{
"error": "User not found",
})
}
if err := c.BodyParser(&user); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request body",
})
}
database.DB.Save(&user)
return c.JSON(fiber.Map{
"success": true,
"data": user,
})
}
func DeleteUser(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
return c.Status(404).JSON(fiber.Map{
"error": "User not found",
})
}
database.DB.Delete(&user)
return c.JSON(fiber.Map{
"success": true,
"message": "User deleted successfully",
})
}
// main.go
package main
import (
"log"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/yourusername/fiber-api/database"
"github.com/yourusername/fiber-api/models"
"github.com/yourusername/fiber-api/routes"
)
func main() {
// Connect to database
database.Connect()
// Run migrations
database.Migrate(&models.User{})
app := fiber.New()
app.Use(logger.New())
routes.Setup(app)
log.Fatal(app.Listen(":3000"))
}
Implement consistent error handling across your API.
func customErrorHandler(c *fiber.Ctx, err error) error {
code := fiber.StatusInternalServerError
message := "Internal Server Error"
if e, ok := err.(*fiber.Error); ok {
code = e.Code
message = e.Message
}
return c.Status(code).JSON(fiber.Map{
"success": false,
"error": message,
})
}
// Use in app configuration
app := fiber.New(fiber.Config{
ErrorHandler: customErrorHandler,
})
// utils/errors.go
package utils
import "github.com/gofiber/fiber/v2"
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func (e *AppError) Error() string {
return e.Message
}
func NewBadRequestError(message string) *AppError {
return &AppError{
Code: fiber.StatusBadRequest,
Message: message,
}
}
func NewNotFoundError(message string) *AppError {
return &AppError{
Code: fiber.StatusNotFound,
Message: message,
}
}
func NewUnauthorizedError(message string) *AppError {
return &AppError{
Code: fiber.StatusUnauthorized,
Message: message,
}
}
// Usage in handlers
func GetUser(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
return utils.NewNotFoundError("User not found")
}
return c.JSON(user)
}
Implement JWT-based authentication.
go get github.com/golang-jwt/jwt/v5
// handlers/auth.go
package handlers
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
"github.com/yourusername/fiber-api/database"
"github.com/yourusername/fiber-api/models"
)
var jwtSecret = []byte("your-secret-key")
type LoginRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}
type RegisterRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
func Register(c *fiber.Ctx) error {
req := new(RegisterRequest)
if err := c.BodyParser(req); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request",
})
}
// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to hash password",
})
}
user := models.User{
Name: req.Name,
Email: req.Email,
Password: string(hashedPassword),
}
if err := database.DB.Create(&user).Error; err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Email already exists",
})
}
return c.Status(201).JSON(fiber.Map{
"success": true,
"message": "User registered successfully",
})
}
func Login(c *fiber.Ctx) error {
req := new(LoginRequest)
if err := c.BodyParser(req); err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Invalid request",
})
}
var user models.User
if err := database.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
return c.Status(401).JSON(fiber.Map{
"error": "Invalid credentials",
})
}
// Check password
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
return c.Status(401).JSON(fiber.Map{
"error": "Invalid credentials",
})
}
// Create JWT token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.ID,
"email": user.Email,
"exp": time.Now().Add(time.Hour * 24).Unix(),
})
tokenString, err := token.SignedString(jwtSecret)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to generate token",
})
}
return c.JSON(fiber.Map{
"success": true,
"token": tokenString,
"user": user,
})
}
// middleware/auth.go
package middleware
import (
"strings"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret = []byte("your-secret-key")
func AuthRequired(c *fiber.Ctx) error {
authHeader := c.Get("Authorization")
if authHeader == "" {
return c.Status(401).JSON(fiber.Map{
"error": "Missing authorization header",
})
}
// Extract token
tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
// Parse token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil || !token.Valid {
return c.Status(401).JSON(fiber.Map{
"error": "Invalid token",
})
}
// Extract claims
claims := token.Claims.(jwt.MapClaims)
c.Locals("user_id", claims["user_id"])
c.Locals("email", claims["email"])
return c.Next()
}
// routes/routes.go
func Setup(app *fiber.App) {
api := app.Group("/api/v1")
// Public routes
api.Post("/register", handlers.Register)
api.Post("/login", handlers.Login)
// Protected routes
users := api.Group("/users", middleware.AuthRequired)
users.Get("/", handlers.GetUsers)
users.Get("/:id", handlers.GetUser)
users.Put("/:id", handlers.UpdateUser)
users.Delete("/:id", handlers.DeleteUser)
}
Handle file uploads and serve static files.
app.Post("/upload", func(c *fiber.Ctx) error {
// Get file from request
file, err := c.FormFile("file")
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "No file uploaded",
})
}
// Save file
err = c.SaveFile(file, "./uploads/"+file.Filename)
if err != nil {
return c.Status(500).JSON(fiber.Map{
"error": "Failed to save file",
})
}
return c.JSON(fiber.Map{
"success": true,
"filename": file.Filename,
"size": file.Size,
})
})
// Multiple files
app.Post("/upload-multiple", func(c *fiber.Ctx) error {
form, err := c.MultipartForm()
if err != nil {
return c.Status(400).JSON(fiber.Map{
"error": "Failed to parse form",
})
}
files := form.File["files"]
for _, file := range files {
c.SaveFile(file, "./uploads/"+file.Filename)
}
return c.JSON(fiber.Map{
"success": true,
"count": len(files),
})
})
// Serve single directory
app.Static("/", "./public")
// Serve with prefix
app.Static("/static", "./public")
// Custom configuration
app.Static("/files", "./uploads", fiber.Static{
Compress: true,
ByteRange: true,
Browse: false,
CacheDuration: 10 * time.Second,
})
Write comprehensive tests for your API.
// handlers/user_test.go
package handlers
import (
"bytes"
"encoding/json"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
)
func TestGetUsers(t *testing.T) {
app := fiber.New()
app.Get("/users", GetUsers)
req := httptest.NewRequest("GET", "/users", nil)
resp, _ := app.Test(req)
assert.Equal(t, 200, resp.StatusCode)
}
func TestCreateUser(t *testing.T) {
app := fiber.New()
app.Post("/users", CreateUser)
user := map[string]interface{}{
"name": "Test User",
"email": "test@example.com",
"age": 25,
}
body, _ := json.Marshal(user)
req := httptest.NewRequest("POST", "/users", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := app.Test(req)
assert.Equal(t, 201, resp.StatusCode)
}
go test ./...
go test -v ./handlers
go test -cover ./...
import (
"os"
"github.com/joho/godotenv"
)
func init() {
godotenv.Load()
}
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
dbURL := os.Getenv("DATABASE_URL")
jwtSecret := os.Getenv("JWT_SECRET")
// Use environment variables...
}
# Dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o main .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 3000
CMD ["./main"]
# Build and run
docker build -t fiber-api .
docker run -p 3000:3000 fiber-api
app := fiber.New(fiber.Config{
Prefork: true, // Enable for production
ServerHeader: "Fiber",
StrictRouting: true,
CaseSensitive: true,
ReadTimeout: time.Second * 10,
WriteTimeout: time.Second * 10,
IdleTimeout: time.Second * 120,
})
You've now learned how to build production-ready REST APIs with Fiber. From basic routing to advanced features like authentication, database integration, and middleware, Fiber provides everything you need to build fast, scalable web services.
Fiber's Express-like syntax makes it approachable for developers from any background, while its performance characteristics ensure your APIs can handle high traffic loads. The extensive middleware ecosystem and active community provide solutions for most common requirements.
Remember, building great APIs is about more than just code—consider security, performance, documentation, and user experience. Test thoroughly, monitor in production, and iterate based on real-world usage.
Happy building with Fiber!