EVO uses Fiber v3 as its web framework, which in turn uses fasthttp under the hood. EVO wraps the Fiber context into a *Request object that adds extra helpers, structured responses, and integration with other EVO subsystems.
package main
import "github.com/getevo/evo/v2"
func main() {
if err := evo.Setup(); err != nil {
panic(err)
}
evo.Get("/hello", func(r *evo.Request) any {
return "hello world"
})
if err := evo.Run(); err != nil {
panic(err)
}
}All route registration functions accept one or more Handler functions:
type Handler func(request *evo.Request) anyevo.Get(path, handlers...)
evo.Post(path, handlers...)
evo.Put(path, handlers...)
evo.Patch(path, handlers...)
evo.Delete(path, handlers...)
evo.Head(path, handlers...)
evo.Options(path, handlers...)
evo.Trace(path, handlers...)
evo.Connect(path, handlers...)
evo.All(path, handlers...) // all HTTP methodsAll functions return fiber.Router for further chaining (e.g. .Name("my-route")).
type Middleware func(request *evo.Request) error// Apply middleware to a path prefix
evo.Use("/api", func(r *evo.Request) error {
token := r.Header("Authorization")
if token == "" {
return r.Context.SendStatus(401)
}
return r.Next()
})
// Catch-all handler (set before evo.Run)
evo.Any = func(r *evo.Request) error {
return r.Context.SendStatus(404)
}api := evo.Group("/api/v1")
api.Get("/users", listUsers)
api.Post("/users", createUser)
// Group with middleware
admin := evo.Group("/admin", authMiddleware)
admin.Get("/dashboard", dashboard)
// Nested groups
v2 := api.Group("/v2")
v2.Get("/users", listUsersV2)
// Named groups (for URL generation)
api.Name("api.")
api.Get("/orders", listOrders) // route name: "api.GET/api/v1/orders"// Single named parameter
evo.Get("/users/:id", func(r *evo.Request) any {
id := r.Param("id").Int()
return id
})
// Multiple parameters
evo.Get("/users/:userId/orders/:orderId", func(r *evo.Request) any {
userId := r.Param("userId").Int64()
orderId := r.Param("orderId").String()
return nil
})
// Wildcard
evo.Get("/files/*", func(r *evo.Request) any {
path := r.Param("*")
return path
})
// All route params as a map
evo.Get("/a/:x/b/:y", func(r *evo.Request) any {
params := r.Params() // map[string]string{"x":"...", "y":"..."}
return params
})
// Override a param value in middleware (before the handler runs)
evo.Use("/", func(r *evo.Request) error {
r.OverrideParam("id", "canonical-id")
return r.Next()
})// GET /search?q=fiber&page=2&active=true
evo.Get("/search", func(r *evo.Request) any {
q := r.Query("q").String()
page := r.Query("page").Int()
active := r.Query("active").Bool()
// All query params at once
all := r.Queries() // map[string]string
return all
})evo.Get("/path", func(r *evo.Request) any {
ct := r.Header("Content-Type")
token := r.Header("Authorization")
// Quick existence check
if r.HasHeader("X-Request-ID") { ... }
// Unique request ID (set by Fiber's RequestID middleware)
id := r.RequestID()
// All request headers
headers := r.ReqHeaders() // map[string]string
_ = ct; _ = token; _ = id
return nil
})type CreateUserDTO struct {
Name string `json:"name" form:"name" query:"name"`
Email string `json:"email" form:"email"`
Age int `json:"age" form:"age"`
}
evo.Post("/users", func(r *evo.Request) any {
var dto CreateUserDTO
if err := r.Bind().Body(&dto); err != nil {
return err
}
return dto
})Bind() automatically selects the decoder based on Content-Type (JSON, XML, form, multipart). You can also bind multiple sources in one chain:
if err := r.Bind().Body(&body).Query(&q).Header(&h); err != nil { ... }evo.Post("/path", func(r *evo.Request) any {
var body MyStruct
if err := r.BodyParser(&body); err != nil {
return err
}
return body
})evo.Post("/raw", func(r *evo.Request) any {
raw := r.Body() // string
b := r.BodyRaw() // []byte — undecoded (no decompression)
_ = b
return raw
})evo.Post("/path", func(r *evo.Request) any {
name := r.ParseJsonBody().Get("user.name").String()
age := r.ParseJsonBody().Get("user.age").Int()
return name + " " + strconv.Itoa(int(age))
})evo.Post("/path", func(r *evo.Request) any {
name := r.FormValue("name").String()
age := r.FormValue("age").Int()
return name
})if !r.HasBody() {
return r.Context.SendStatus(400)
}evo.Post("/upload", func(r *evo.Request) any {
file, err := r.FormFile("avatar")
if err != nil {
return err
}
// Save to local disk
return r.SaveFile(file, "./uploads/"+file.Filename)
})SaveFileToStorage writes the upload to any backend that satisfies the fiber.Storage interface (e.g. S3, Redis, in-memory):
evo.Post("/upload", func(r *evo.Request) any {
file, err := r.FormFile("avatar")
if err != nil {
return err
}
return r.SaveFileToStorage(file, "avatars/"+file.Filename, myS3Storage)
})evo.Get("/info", func(r *evo.Request) any {
full := r.FullURL() // https://example.com/info?x=1
base := r.BaseURL() // https://example.com
path := r.Path() // /info
original := r.OriginalURL() // /info?x=1
host := r.Hostname() // example.com (no port)
rawHost := r.Host() // example.com:8080 (includes port when present)
proto := r.Protocol() // https
scheme := r.Scheme() // https (alias for Protocol)
port := r.Port() // 443
method := r.Method() // GET
secure := r.IsSecure() // true
local := r.IsFromLocal() // false
xhr := r.XHR() // false
referer := r.Referer() // "https://other.com"
ips := r.IPs() // []string
ip := r.IP() // "203.0.113.5"
fullPath := r.FullPath() // "/info" — matched route pattern (e.g. "/users/:id")
_ = full; _ = base; _ = path; _ = original; _ = host; _ = rawHost
_ = proto; _ = scheme; _ = port; _ = method; _ = secure; _ = local
_ = xhr; _ = referer; _ = ips; _ = ip; _ = fullPath
return nil
})evo.Get("/ws", func(r *evo.Request) any {
if r.IsWebSocket() {
// handle WebSocket upgrade
}
if r.IsPreflight() {
// CORS preflight OPTIONS request
r.Set("Access-Control-Allow-Origin", "*")
return r.SendStatus(204)
}
if r.IsProxyTrusted() {
// request came from a trusted proxy range
}
return nil
})evo.Get("/video", func(r *evo.Request) any {
fileSize := int64(1_000_000)
rng, err := r.Range(fileSize)
if err != nil {
return r.SendStatus(416) // Range Not Satisfiable
}
// rng.Type — "bytes"
// rng.Ranges — []fiber.RangeSet, each has Start and End offsets
r.Status(206)
return r.SendFile("./video.mp4")
})mt := r.MediaType() // "application/json"
cs := r.Charset() // "utf-8"
ok := r.Is("json") // true if Content-Type is application/json
// Quick boolean checks
r.IsJSON() // Content-Type: application/json
r.IsForm() // Content-Type: application/x-www-form-urlencoded
r.IsMultipart() // Content-Type: multipart/form-data// Boolean shortcuts
r.AcceptsJSON() // true when Accept allows application/json
r.AcceptsHTML() // true when Accept allows text/html
r.AcceptsXML() // true when Accept allows application/xml or text/xml
r.AcceptsEventStream() // true when Accept allows text/event-stream (SSE)
// Single best-match values
lang := r.AcceptLanguage() // e.g. "en-US"
enc := r.AcceptEncoding() // e.g. "gzip"
// Multi-offer negotiation (returns the best match or "")
best := r.AcceptsLanguages("en", "fr", "de")
best = r.AcceptsLanguagesExtended("en-US", "en", "fr") // RFC 4647 subtag matching
best = r.AcceptsCharsets("utf-8", "iso-8859-1")
best = r.AcceptsEncodings("gzip", "deflate", "identity")evo.Get("/users/:id", func(r *evo.Request) any {
user := getUser(r.Param("id").Int())
return user // auto-wrapped in {"success":true,"data":{...}}
})
// Or send directly without the wrapper:
r.JSON(user)r.XML(myStruct) // Content-Type: application/xmlr.SendString("hello")
r.SendHTML("<h1>hello</h1>")// Chooses format based on Accept header (JSON, XML, text, …)
r.AutoFormat(myData)r.SendStatus(204)
r.Status(201).JSON(created)r.SendFile("./report.pdf")
r.Download("./report.pdf", "monthly-report.pdf")r.MsgPack(myStruct) // Content-Type: application/msgpack
r.CBOR(myStruct) // Content-Type: application/cbor// io.Reader — Content-Length unknown
evo.Get("/stream", func(r *evo.Request) any {
pr, pw := io.Pipe()
go func() {
defer pw.Close()
for i := 0; i < 5; i++ {
fmt.Fprintf(pw, "chunk %d\n", i)
}
}()
return r.SendStream(pr)
})
// Buffered writer — more control over flushing
evo.Get("/stream2", func(r *evo.Request) any {
return r.SendStreamWriter(func(w *bufio.Writer) {
for i := 0; i < 5; i++ {
fmt.Fprintf(w, "data: chunk %d\n\n", i)
w.Flush()
}
})
})evo.Get("/events", func(r *evo.Request) any {
r.Set("Content-Type", "text/event-stream")
r.Set("Cache-Control", "no-cache")
return r.SendStreamWriter(func(w *bufio.Writer) {
for i := 0; i < 10; i++ {
fmt.Fprintf(w, "data: event %d\n\n", i)
w.Flush()
time.Sleep(500 * time.Millisecond)
}
r.End() // terminate the stream
})
})Instruct the browser to preload resources before the final response:
evo.Get("/page", func(r *evo.Request) any {
r.SendEarlyHints([]string{
"</style.css>; rel=preload; as=style",
"</app.js>; rel=preload; as=script",
})
// ... generate the actual response
return r.SendFile("./index.html")
})r.Writef("Hello, %s! You have %d messages.\n", name, count)
r.WriteString("plain text append")r.Redirect("/new-path") // 303 See Other (Fiber v3 default)
r.Redirect("/new-path", 301) // Permanent redirect
r.Redirect("/new-path", 302) // Temporary redirect
// Package-level redirects (registered at startup)
evo.Redirect("/old", "/new") // 307
evo.RedirectPermanent("/old", "/new") // 301
evo.RedirectTemporary("/old", "/new") // 302r.Write([]byte("raw bytes"))
r.Write("string")
r.Write(42)r.Drop() // closes TCP connection without sending any responser.Set("X-Custom-Header", "value")
r.SetHeader("X-Custom-Header", "value") // alias
r.AppendHeader("Vary", "Accept-Encoding")
r.Type("json") // sets Content-Type by extension
r.Vary("Accept-Language")
r.Links("http://api.example.com/users?page=2; rel=\"next\"")
r.Attachment("report.pdf") // Content-Disposition: attachment
// Read a single response header that was already set
ct := r.GetRespHeader("Content-Type") // "application/json"
ct = r.GetRespHeader("X-Missing", "fallback") // "fallback"
// All response headers
headers := r.RespHeaders() // map[string]stringevo.Get("/cookies", func(r *evo.Request) any {
// Read
val := r.Cookie("session")
// Set simple value
r.SetCookie("session", "abc123")
// Set with expiry
r.SetCookie("session", "abc123", 24*time.Hour)
// Set complex value (JSON-encoded + base64)
r.SetCookie("prefs", map[string]any{"theme": "dark"})
// Full control
r.SetRawCookie(&outcome.Cookie{
Name: "session",
Value: "abc123",
Path: "/",
Domain: "example.com",
Expires: time.Now().Add(24 * time.Hour),
Secure: true,
HTTPOnly: true,
SameSite: "Strict",
})
// Clear
r.ClearCookie("session")
_ = val
return nil
})// Set in middleware
evo.Use("/", func(r *evo.Request) error {
r.Context.Locals("userID", 42)
return r.Next()
})
// Read in handler
evo.Get("/profile", func(r *evo.Request) any {
id := r.Var("userID").Int() // generic.Value wrapper
return id
})// Serve ./public at /
evo.Static("/", "./public")
// Serve ./assets at /static with options
evo.Static("/static", "./assets", static.Config{
Browse: false, // disable directory listing
Compress: true, // compress responses
ByteRange: true, // enable range requests
MaxAge: 3600, // Cache-Control max-age in seconds
})evo.Get("/users/:id", listUser).Name("users.show")
// In a handler, generate a URL for a named route
url := r.Route("users.show", "id", 42) // "/users/42"// Attach values via Locals (recommended for evo handlers)
evo.Use("/", func(r *evo.Request) error {
r.Context.Locals("requestID", uuid.New().String())
return r.Next()
})
// Replace the request's context.Context (e.g. to attach a deadline or tracing span)
evo.Use("/", func(r *evo.Request) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
r.SetContext(ctx)
return r.Next()
})import "github.com/getevo/evo/v2/lib/outcome"
evo.Get("/orders/:id", func(r *evo.Request) any {
order, err := db.FindOrder(r.Param("id").Int())
if err != nil {
return outcome.NotFound("order not found")
}
return outcome.OK(order)
})See outcome.md for the full list of status helpers.
// Default — waits indefinitely for connections to drain
evo.Shutdown()
// With timeout
evo.ShutdownWithTimeout(30 * time.Second)
// With context
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
evo.ShutdownWithContext(ctx)// All routes (including middleware)
routes := evo.GetRoutes()
// Only non-middleware routes
routes := evo.GetRoutes(true)
for _, r := range routes {
fmt.Println(r.Method, r.Path, r.Name)
}fiberApp := evo.GetFiber() // *fiber.AppHTTP server settings are controlled via settings.yml (or environment variables) under the [HTTP] section:
| Key | Default | Description |
|---|---|---|
Host |
"" |
Listen address |
Port |
8080 |
Listen port |
Prefork |
false |
Enable prefork (multi-process) |
ServerHeader |
"" |
Server header value; also used as the trusted proxy header for IP detection |
StrictRouting |
false |
Treat /foo and /foo/ as different |
CaseSensitive |
false |
Case-sensitive routing |
BodyLimit |
4194304 |
Max request body size (bytes) |
Concurrency |
262144 |
Max concurrent connections |
ReadTimeout |
0 |
Read deadline per connection |
WriteTimeout |
0 |
Write deadline per connection |
IdleTimeout |
0 |
Keep-alive idle timeout |
ReadBufferSize |
4096 |
Per-connection read buffer |
WriteBufferSize |
4096 |
Per-connection write buffer |
GETOnly |
false |
Accept only GET requests |
DisableKeepalive |
false |
Disable keep-alive |
ReduceMemoryUsage |
false |
Reduce memory at the cost of CPU |
EnablePrintRoutes |
false |
Print all routes on startup |