Skip to main content

Controllers & Deserialization

Controllers are the main way to interact with the application. They are responsible for handling the requests and responses.

Controller types

The standard controller type is a function that receives a fuego.ContextWithBody and returns a response and an error.

// Standard fuego controller
func MyController(c fuego.ContextWithBody[Body]) (MyResponse, error)

Fuego handles Content Negotiation, so you can serve different content types based on the Accept header of the request. One controller, multiple responses formats.

Just care about data types! Fuego will handle the rest.

Please note that Fuego relies on the underlying libraries (net/http, encoding/json, gin if using fuegogin) to handle deserialization. We avoid at all cost the use of reflection to keep the performance high.

Registering a controller

See Routing for more information.

fuego.Get(s, "/", MyController)

Request body

Use the fuego.ContextWithBody interface. Useful for POST, PUT, PATCH requests for example.

type MyInput struct {
Name string `json:"name"`
}

func MyController(c fuego.ContextWithBody[MyInput]) (*MyResponse, error) {
body, err := c.Body()
if err != nil {
return nil, err
}

return &MyResponse{
Name: body.Name,
}, nil
}

Fuego will automatically parse the request body (JSON, XML, YAML, application/x-www-form-urlencoded and multipart/form-data) according to the Content-Type header of the request.

curl -X POST http://localhost:9999/ -d '{"name": "My name"}' -H "Content-Type: application/json"
# Response: {"name": "My name"}

curl -X POST http://localhost:9999/ -d '<MyInput><Name>My name</Name></MyInput>' -H "Content-Type: application/xml"
# Response: {"name": "My name"}

It will then validate it using the input struct, see Validation.

I don't need request body

Use the fuego.ContextNoBody interface. Useful for GET, DELETE, HEAD, OPTIONS requests for example.

func MyController(c fuego.ContextNoBody) (MyResponse, error) {
return MyResponse{Name: "My name"}, nil
}

Binary body

If you just want to read the body of the request as a byte slice, you can use the []byte receiver type.

Don't forget to set the request Content-Type header to application/octet-stream.

fuego.Put(s, "/blob", func(c fuego.ContextWithBody[[]byte]) (any, error) {
body, err := c.Body()
if err != nil {
return nil, err
}

return body, nil
})

Query parameters (dynamic)

They are declared (for OpenAPI and validation) at the route registration level. It is not type-safe (it relies on the same string on the route registration and the controller) BUT it raises warning if you make a typo and use a non-declared query parameter.

See Route Options for more information.

package main

import (
"github.com/go-fuego/fuego"
)

type MyInput struct {
Name string `json:"name"`
}

func myController(c fuego.ContextWithBody[MyInput]) (*MyResponse, error) {
name := c.QueryParam("name")
return &MyResponse{
Name: name,
}, nil
}

var myReusableOption = option.Group(
option.QueryInt("per_page", "Number of items per page", param.Default(100), param.Example("100 per page", 100)),
option.QueryInt("page", "Page number", param.Default(1), param.Example("page 9", 9)),
)

func main() {
s := fuego.NewServer()

fuego.Get(s, "/", myController,
option.Query("name", "Name of the user", param.Required(), param.Example("example 1", "Napoleon")),
myReusableOption,
)

s.Run()
}
curl -X GET http://localhost:9999/?name=MyName
# Response: {"name": "MyName"}

Query parameters (type-safe)

🚧 Below contains incoming syntax, not available currently

This syntax allows users to have strong static typing between the input and the query parameters.

type Params struct {
Limit int `query:"limit"`
Group string `header:"X-User-Group"`
}
func myController(c fuego.Context[MyInput, Params]) (*MyResponse, error) {
params, err := c.Params()
if err != nil {
return nil, err
}

return &MyResponse{
Name: params.Group,
}, nil
}

Headers

You can always go further in the request and response by using the underlying net/http request and response, by using c.Request and c.Response.

Get request header

func MyController(c fuego.ContextNoBody) (MyResponse, error) {
value := c.Header("X-My-Header")
return MyResponse{}, nil
}

Set response header

func MyController(c fuego.ContextNoBody) (MyResponse, error) {
c.SetHeader("X-My-Header", "value")
return MyResponse{}, nil
}

Cookies

func MyController(c fuego.ContextNoBody) (MyResponse, error) {
value := c.Cookie("my-cookie")
return MyResponse{}, nil
}
func MyController(c fuego.ContextNoBody) (MyResponse, error) {
c.SetCookie("my-cookie", "value")
return MyResponse{}, nil
}