Go Fiber
last modified October 30, 2026
In this article we show how to create web applications in Go with Fiber. Fiber is an Express-inspired framework built on top of fasthttp; it provides routing, middleware, static file serving and template rendering with a small API.
The examples use Fiber v3, where a handler receives a fiber.Ctx
interface and returns an error. All examples were compiled and run with Go
1.27.0 and Fiber v3.6.1; they listen on port 3001, so run one at a time.
Installation
Fiber is a module dependency. The import path contains the major version, and the startup message can be disabled when the program prints its own log line.
$ mkdir fiber-demo $ cd fiber-demo $ go mod init com.zetcode/fiber go: creating new go.mod: module com.zetcode/fiber $ go get github.com/gofiber/fiber/v3@v3.6.1 2>&1 | grep 'fiber/v3' go: added github.com/gofiber/fiber/v3 v3.6.1 $ go get github.com/gofiber/template/html/v3@v3.0.12 2>&1 | grep 'template/html' go: added github.com/gofiber/template/html/v3 v3.0.12
The first application
fiber.New creates an application, Get registers a
route and Listen starts the server. A handler returns
c.SendString, which writes a text response with status 200.
package main
import (
"log"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello from Fiber")
})
log.Println("listening on http://localhost:3001")
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
ListenConfig suppresses the multi-line banner that Fiber normally
prints, which keeps the output of the example small. The response has the text
content type and a content length of 16 bytes.
$ go run main.go & 2026/10/30 20:40:24 listening on http://localhost:3001 $ curl -i localhost:3001/ HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 16 Hello from Fiber
Status codes and headers
c.Status sets the status code and returns the context, so it can
be chained with another response method. c.Set adds a response
header. c.SendStatus sends only the status and its standard text.
package main
import (
"log"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/created", func(c fiber.Ctx) error {
c.Set("X-App", "fiber-demo")
return c.SendStatus(fiber.StatusCreated)
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
SendStatus(201) writes the standard reason phrase
Created as the body. For an empty body, use
c.Status(fiber.StatusCreated).Send(nil) or
c.SendStatus together with a no-content status.
$ curl -i localhost:3001/created HTTP/1.1 201 Created Content-Type: text/plain; charset=utf-8 Content-Length: 7 X-App: fiber-demo Created
JSON responses
c.JSON encodes any value with encoding/json and sets
the JSON content type. Struct tags control the field names.
package main
import (
"log"
"github.com/gofiber/fiber/v3"
)
type Movie struct {
ID int `json:"id"`
Title string `json:"title"`
}
func main() {
app := fiber.New()
app.Get("/movies", func(c fiber.Ctx) error {
movies := []Movie{
{ID: 1, Title: "Toy Story"},
{ID: 2, Title: "The Raid"},
{ID: 3, Title: "Hero"},
}
return c.JSON(movies)
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
The slice is encoded as a JSON array and every struct as an object. Fiber sets
Content-Type: application/json; charset=utf-8.
$ curl localhost:3001/movies
[{"id":1,"title":"Toy Story"},{"id":2,"title":"The Raid"},{"id":3,"title":"Hero"}]
Query parameters
c.Query returns a query string value and accepts a default for a
missing parameter. The default makes optional parameters explicit.
package main
import (
"fmt"
"log"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/hello", func(c fiber.Ctx) error {
name := c.Query("name", "guest")
age := c.Query("age", "unknown")
return c.SendString(fmt.Sprintf("%s is %s years old", name, age))
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
The query string is not part of the route, so one handler serves every combination of parameters. Missing values are replaced by the defaults.
$ curl 'localhost:3001/hello?name=Peter&age=45'; echo Peter is 45 years old $ curl localhost:3001/hello; echo guest is unknown years old
Path parameters
A colon in the route declares a path parameter. c.Params returns
the matched text, which still has to be converted to a number when the route
expects an identifier.
package main
import (
"fmt"
"log"
"strconv"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/users/:id", func(c fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "invalid id")
}
return c.SendString(fmt.Sprintf("user %d", id))
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
A valid number is echoed back. A value that cannot be converted returns a
fiber.Error, which the default error handler turns into a 400
response with the error message.
$ curl localhost:3001/users/42; echo user 42 $ curl -i localhost:3001/users/abc HTTP/1.1 400 Bad Request Content-Type: text/plain; charset=utf-8 Content-Length: 10 invalid id
Request headers
c.Get reads a request header. It also accepts a default value,
which is useful for optional headers such as API tokens.
package main
import (
"fmt"
"log"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
app.Get("/agent", func(c fiber.Ctx) error {
ua := c.Get("User-Agent")
token := c.Get("X-Token", "anonymous")
return c.SendString(fmt.Sprintf("agent=%s token=%s", ua, token))
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
The user agent is always sent by curl, while the custom token is provided only in the second request. Header names are case-insensitive.
$ curl localhost:3001/agent; echo agent=curl/7.81.0 token=anonymous $ curl -H 'X-Token: secret' -A 'test-agent' localhost:3001/agent; echo agent=test-agent token=secret
Static files and downloads
In Fiber v3 static file serving is a middleware. static.New
serves a directory, c.SendFile sends a single file for display and
c.Download sends it as an attachment.
package main
import (
"log"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/static"
)
func main() {
app := fiber.New()
app.Use(static.New("./public"))
app.Get("/download", func(c fiber.Ctx) error {
return c.Download("./assets/logo.png")
})
app.Get("/view", func(c fiber.Ctx) error {
return c.SendFile("./assets/logo.png")
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
The static middleware serves index.html for the root path. The
download response adds Content-Disposition, while the view
response does not, so a browser displays the image instead of saving it.
$ curl -i localhost:3001/ HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 93 <!DOCTYPE html> ... $ curl -I localhost:3001/download HTTP/1.1 200 OK Content-Type: image/png Content-Length: 219 Content-Disposition: attachment; filename="logo.png" $ curl -I localhost:3001/view HTTP/1.1 200 OK Content-Type: image/png Content-Length: 219
HTML templates
The html template engine uses the standard
html/template syntax. It is registered in the application
configuration, and c.Render renders a named template with data.
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>Show</title></head>
<body><p>{{ .Name }} has {{ .Count }} messages.</p></body>
</html>
The template contains two actions and no application logic; the values are supplied by the handler.
package main
import (
"log"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/template/html/v3"
)
func main() {
app := fiber.New(fiber.Config{
Views: html.New("./views", ".html"),
})
app.Get("/show", func(c fiber.Ctx) error {
return c.Render("show", fiber.Map{
"Name": "Lucia",
"Count": 3,
})
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
The engine adds the file extension, so the template name is
show. The values are passed in a map and inserted at the
{{ }} actions by the template engine.
$ curl localhost:3001/show <!DOCTYPE html> <html lang="en"> <head><meta charset="utf-8"><title>Show</title></head> <body><p>Lucia has 3 messages.</p></body> </html>
Groups, middleware and errors
app.Group prefixes a set of routes, app.Use adds
middleware, and c.Next continues with the next handler. A
middleware registered after the routes acts as the not-found handler.
package main
import (
"log"
"github.com/gofiber/fiber/v3"
)
func requestID(c fiber.Ctx) error {
c.Set("X-Request-ID", "demo-123")
return c.Next()
}
func main() {
app := fiber.New()
app.Use(requestID)
api := app.Group("/api")
api.Get("/users", func(c fiber.Ctx) error {
return c.JSON(fiber.Map{"users": []string{"Lucia", "Peter"}})
})
app.Get("/fail", func(c fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "bad input")
})
app.Use(func(c fiber.Ctx) error {
return c.Status(fiber.StatusNotFound).SendString("custom not found")
})
log.Fatal(app.Listen(":3001", fiber.ListenConfig{DisableStartupMessage: true}))
}
The request ID middleware runs for every route and adds the header to both successful and failed responses. The group keeps the API prefix in one place, and the final middleware answers every unmatched path with 404.
$ curl -i localhost:3001/api/users; echo
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 27
X-Request-Id: demo-123
{"users":["Lucia","Peter"]}
$ curl -i localhost:3001/fail; echo
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Content-Length: 9
X-Request-Id: demo-123
bad input
$ curl -i localhost:3001/nope; echo
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
Content-Length: 16
X-Request-Id: demo-123
custom not found
Source
Go Fiber - Github page
Fiber - documentation
In this article we have created web applications in Go with Fiber. We started a server, set status codes and headers, returned JSON, read query, path and header values, served static files and downloads, rendered an HTML template, and combined groups, middleware, errors and a custom 404 handler.
Author
List all Go tutorials.