Episode 7 of 8

Send, SendStatus, SendFile & Status

Master every response method in Go Fiber. Learn the differences between Send, SendString, SendStatus, SendFile, Status, JSON, XML, and streaming responses. Understand when to use each method and how to craft precise HTTP responses for every scenario.

Fiber provides multiple response methods — each designed for specific use cases. Choosing the right method ensures correct content types, proper status codes, and efficient data transfer. This tutorial covers every response method with practical examples showing when and why to use each one.

c.Status() — Setting HTTP Status Codes

c.Status() sets the HTTP status code for the response. It returns the Fiber context, enabling method chaining:

// Status with JSON response
app.Post("/users", func(c *fiber.Ctx) error {
    // 201 Created — resource successfully created
    return c.Status(fiber.StatusCreated).JSON(fiber.Map{
        "message": "User created",
        "id":      123,
    })
})

// Common status codes
app.Get("/examples", func(c *fiber.Ctx) error {
    // 200 OK (default — you don't need to set it explicitly)
    return c.JSON(fiber.Map{"status": "ok"})
})

app.Get("/moved", func(c *fiber.Ctx) error {
    // 301 Moved Permanently
    return c.Status(fiber.StatusMovedPermanently).Redirect("/new-location")
})

app.Get("/secret", func(c *fiber.Ctx) error {
    // 403 Forbidden
    return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
        "error": "Access denied",
    })
})

app.Get("/broken", func(c *fiber.Ctx) error {
    // 500 Internal Server Error
    return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
        "error": "Something went wrong",
    })
})

Fiber provides named constants for all HTTP status codes:

fiber.StatusOK                  // 200
fiber.StatusCreated             // 201
fiber.StatusNoContent           // 204
fiber.StatusMovedPermanently    // 301
fiber.StatusFound               // 302
fiber.StatusBadRequest          // 400
fiber.StatusUnauthorized        // 401
fiber.StatusForbidden           // 403
fiber.StatusNotFound            // 404
fiber.StatusConflict            // 409
fiber.StatusUnprocessableEntity // 422
fiber.StatusTooManyRequests     // 429
fiber.StatusInternalServerError // 500
fiber.StatusServiceUnavailable  // 503

c.Send() — Send Raw Bytes

c.Send() sends a raw byte slice as the response body. Use it when you have binary data or pre-formatted content:

// Send raw bytes
app.Get("/raw", func(c *fiber.Ctx) error {
    data := []byte("Raw byte response")
    return c.Send(data)
})

// Send generated binary data (e.g., a QR code)
app.Get("/qr/:data", func(c *fiber.Ctx) error {
    data := c.Params("data")
    
    // Generate QR code as PNG bytes
    qrBytes, err := generateQRCode(data)
    if err != nil {
        return c.Status(500).SendString("Failed to generate QR code")
    }
    
    c.Set("Content-Type", "image/png")
    return c.Send(qrBytes)
})

// Send from a reader
app.Get("/stream", func(c *fiber.Ctx) error {
    file, err := os.Open("./data/large-file.bin")
    if err != nil {
        return c.Status(404).SendString("File not found")
    }
    defer file.Close()
    
    data, err := io.ReadAll(file)
    if err != nil {
        return c.Status(500).SendString("Read error")
    }
    
    c.Set("Content-Type", "application/octet-stream")
    return c.Send(data)
})

c.SendString() — Send Text

c.SendString() sends a string response with Content-Type: text/plain:

// Simple text response
app.Get("/ping", func(c *fiber.Ctx) error {
    return c.SendString("pong")
})

// With status code
app.Get("/health", func(c *fiber.Ctx) error {
    if isHealthy {
        return c.SendString("OK")
    }
    return c.Status(503).SendString("Service Unavailable")
})

// Dynamic string building
app.Get("/greeting/:name", func(c *fiber.Ctx) error {
    name := c.Params("name")
    return c.SendString(fmt.Sprintf("Hello, %s! Welcome to the API.", name))
})

c.SendStatus() — Status Code as Response

c.SendStatus() sets the status code AND sends the status text as the response body. It's a shorthand for when you just need to indicate success or failure without a body:

// 200 — sends "OK" as the body
app.Get("/check", func(c *fiber.Ctx) error {
    return c.SendStatus(fiber.StatusOK) 
    // Response body: "OK"
})

// 204 No Content — typically after a successful DELETE
app.Delete("/users/:id", func(c *fiber.Ctx) error {
    id := c.Params("id")
    if err := deleteUser(id); err != nil {
        return c.Status(404).JSON(fiber.Map{"error": "User not found"})
    }
    return c.SendStatus(fiber.StatusNoContent)
    // Response body: "No Content"
})

// 202 Accepted — for async operations
app.Post("/reports/generate", func(c *fiber.Ctx) error {
    // Queue report generation
    go generateReport()
    return c.SendStatus(fiber.StatusAccepted)
    // Response body: "Accepted"
})

// 501 Not Implemented
app.Get("/v2/feature", func(c *fiber.Ctx) error {
    return c.SendStatus(fiber.StatusNotImplemented)
    // Response body: "Not Implemented"
})

Difference between Status() and SendStatus():
c.Status(404) — sets the status code, you still need to send a response body.
c.SendStatus(404) — sets the status code AND sends "Not Found" as the body. It's a one-liner.

c.SendFile() — Serve Files

// Serve a static file
app.Get("/resume", func(c *fiber.Ctx) error {
    return c.SendFile("./files/resume.pdf")
    // Browser displays the PDF inline
})

// Serve with custom content type
app.Get("/data.csv", func(c *fiber.Ctx) error {
    c.Set("Content-Type", "text/csv")
    return c.SendFile("./exports/data.csv")
})

// Serve with caching headers
app.Get("/assets/:filename", func(c *fiber.Ctx) error {
    filename := c.Params("filename")
    filepath := fmt.Sprintf("./public/assets/%s", filename)
    
    // Check if file exists
    if _, err := os.Stat(filepath); os.IsNotExist(err) {
        return c.SendStatus(fiber.StatusNotFound)
    }
    
    // Set cache headers for static assets
    c.Set("Cache-Control", "public, max-age=31536000, immutable")
    
    return c.SendFile(filepath)
})

// Conditional file serving
app.Get("/avatar/:userId", func(c *fiber.Ctx) error {
    userID := c.Params("userId")
    
    // Try user's custom avatar
    customPath := fmt.Sprintf("./uploads/avatars/%s.jpg", userID)
    if _, err := os.Stat(customPath); err == nil {
        return c.SendFile(customPath)
    }
    
    // Fall back to default avatar
    return c.SendFile("./public/images/default-avatar.png")
})

c.JSON() — JSON Responses

// Struct serialization
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
    Role  string `json:"role"`
}

app.Get("/user", func(c *fiber.Ctx) error {
    user := User{
        ID:    1,
        Name:  "John Doe",
        Email: "john@example.com",
        Role:  "admin",
    }
    return c.JSON(user)
    // {"id":1,"name":"John Doe","email":"john@example.com","role":"admin"}
})

// fiber.Map for ad-hoc responses
app.Get("/status", func(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{
        "status":    "healthy",
        "version":   "2.1.0",
        "uptime":    time.Since(startTime).String(),
        "timestamp": time.Now().Unix(),
    })
})

// Slices
app.Get("/tags", func(c *fiber.Ctx) error {
    tags := []string{"go", "fiber", "api", "tutorial"}
    return c.JSON(tags)
    // ["go","fiber","api","tutorial"]
})

// Nested structures
app.Get("/dashboard", func(c *fiber.Ctx) error {
    return c.JSON(fiber.Map{
        "stats": fiber.Map{
            "users":    1500,
            "orders":   342,
            "revenue":  28500.50,
        },
        "recent_orders": []fiber.Map{
            {"id": 101, "total": 49.99, "status": "shipped"},
            {"id": 102, "total": 129.00, "status": "pending"},
        },
    })
})

c.XML() — XML Responses

type Product struct {
    XMLName xml.Name `xml:"product"`
    ID      int      `xml:"id"`
    Name    string   `xml:"name"`
    Price   float64  `xml:"price"`
}

app.Get("/product.xml", func(c *fiber.Ctx) error {
    product := Product{
        ID:    1,
        Name:  "Go Programming Book",
        Price: 39.99,
    }
    return c.XML(product)
})

c.Redirect() — URL Redirects

// Temporary redirect (302)
app.Get("/old-page", func(c *fiber.Ctx) error {
    return c.Redirect("/new-page")
})

// Permanent redirect (301)
app.Get("/legacy/api", func(c *fiber.Ctx) error {
    return c.Redirect("/api/v2", fiber.StatusMovedPermanently)
})

// Dynamic redirect
app.Get("/short/:code", func(c *fiber.Ctx) error {
    code := c.Params("code")
    fullURL := resolveShortURL(code)
    
    if fullURL == "" {
        return c.SendStatus(404)
    }
    
    return c.Redirect(fullURL, fiber.StatusFound)
})

Response Headers and Content Types

app.Get("/custom", func(c *fiber.Ctx) error {
    // Set response headers
    c.Set("X-Custom-Header", "custom-value")
    c.Set("X-Request-ID", uuid.New().String())
    
    // Set Content-Type explicitly
    c.Set("Content-Type", "text/html; charset=utf-8")
    
    return c.SendString("<h1>Hello HTML</h1>")
})

// Convenience methods
app.Get("/types", func(c *fiber.Ctx) error {
    // These are equivalent:
    c.Set("Content-Type", "application/json")
    // or
    c.Type("json")   // Sets Content-Type to application/json
    c.Type("html")   // Sets Content-Type to text/html
    c.Type("png")    // Sets Content-Type to image/png
    
    return c.JSON(fiber.Map{"type": "example"})
})

Response Method Cheat Sheet

MethodUse WhenSets Content-Type
c.Send([]byte)Raw binary dataNo (set manually)
c.SendString(string)Plain text responsestext/plain
c.SendStatus(int)Status code only, no body neededtext/plain
c.SendFile(path)Serve a file inlineAuto-detected
c.Download(path)Force file downloadAuto + attachment
c.JSON(v)JSON API responsesapplication/json
c.XML(v)XML responsesapplication/xml
c.Redirect(url)URL redirectsN/A
c.Status(int)Set status code (chainable)N/A (chain another method)

What's Next

You now have the complete toolkit for crafting HTTP responses in Fiber. In the final tutorial of this series, we'll build a complete API with JWT authentication — bringing together everything you've learned into a production-ready application with login, token generation, protected routes, and refresh tokens.

GoFiberTutorialAPI