Fuego Go API Framework

repository·main·Indexed 23 days ago

https://github.com/go-fuego/fuego

A modern Go API framework that automatically generates OpenAPI documentation from code using generics. Fuego provides high-level routing, validation, serialization, and error handling while maintaining 100% compatibility with the standard net/http library. It includes a CLI for generating controllers and services, support for custom data transformation via InTransform and OutTransform interfaces, and a generic Context system for handling request bodies and parameters.

Tokens
44.5K
Snippets
117
Records
238
Agent score
82%

What's inside Fuego

  1. How transformation handles nested structs

    main

    Transformation in Fuego is not recursive. If your struct contains nested structs, you must manually call the transformation method of the nested struct within the parent's transformation method. This design provides explicit control and avoids 'magic' behavior.

    type Address struct {
    	Street string `json:"street"`
    	City   string `json:"city"`
    }
    
    func (a *Address) InTransform(ctx context.Context) error {
    	a.Street = strings.TrimSpace(a.Street)
    	a.City = strings.ToUpper(a.City)
    	return nil
    }
    
    type User struct {
    	FirstName string `json:"first_name"`
    	LastName  string `json:"last_name"`
    	Address   Address `json:"address"` 
    }
    
    func (u *User) InTransform(ctx context.Context) error {
    	u.FirstName = strings.ToUpper(u.FirstName)
    	u.LastName = strings.TrimSpace(u.LastName)
    
    	// Manually transform the nested struct
    	err := u.Address.InTransform(ctx)
    	if err != nil {
    		return err
    	}
    
    	return nil
    }
  2. Use net/http middlewares in Fuego

    main

    Fuego is compatible with all net/http middlewares, including those from popular libraries like chi and gorilla. You can use them to implement cross-cutting concerns such as logging, authentication, and CORS.

    Middlewares can be applied at two distinct levels:

    1. Route middlewares: Scoped to specific routes or groups of routes.
    2. Global middlewares: Applied to every single request, including non-matching routes (essential for handling OPTIONS requests in CORS).
  3. Automatic Content Negotiation with Accept Headers

    main

    Fuego implements HTTP content negotiation out of the box. Your API automatically responds with different formats based on the client's Accept header without requiring additional code. If no Accept header is provided, Fuego defaults to JSON (application/json).

    Supported formats include:

    • JSON: application/json (default)
    • XML: application/xml
    • YAML: application/yaml
    • HTML: text/html
    • Plain text: text/plain
    type MyReturnType struct {
    	Message string `json:"message"`
    }
    
    func helloWorld(c fuego.ContextNoBody) (MyReturnType, error) {
    	return MyReturnType{Message: "Hello, World!"}, nil
    }
    
    // curl request: curl -X GET http://localhost:8080/ -H "Accept: application/json"
    // response: {"message":"Hello, World!"}
    
    // curl request: curl -X GET http://localhost:8080/ -H "Accept: application/xml"
    // response: <MyReturnType><Message>Hello, World!</Message></MyReturnType>
  4. Inherit route options via Groups

    main

    When using fuego.Group, you can pass route options that will be inherited by all routes registered within that group.

    Note: The maintainers recommend using option.Group instead of group-level inheritance for a more composable approach.

    package main
    
    import (
    	"github.com/go-fuego/fuego"
    	"github.com/go-fuego/fuego/option"
    )
    
    func main() {
    	s := fuego.NewServer()
    
    	g := fuego.Group(s, "/pets",
    		option.Summary("Pets operations"),
    		option.Description("Operations about pets"),
    		option.Tags("pets"),
    	)
    
    	fuego.Get(g, "/", func(c fuego.ContextNoBody) (string, error) {
    		return "Hello, World!", nil
    	})
    }
  5. Use Group Options and Custom Options for OpenAPI

    main

    Fuego allows you to apply OpenAPI customizations to entire groups of routes or create reusable sets of options.

    Group Options

    When creating a group via fuego.Group, you can pass options that will apply to all routes within that group (e.g., a default option.Summary or option.Tags).

    Option Groups

    You can define reusable sets of options using option.Group. This is useful for common patterns like pagination.

    Custom Options

    You can define a custom function that takes a *fuego.BaseRoute to inject custom behavior directly into the route struct.

    // Define a reusable group of options
    var optionPagination = option.Group(
    	option.QueryInt("page", "Page number", param.Default(1)),
    	option.QueryInt("limit", "Items per page", param.Default(10)),
    )
    
    // Custom options for the group
    var customOption = func(r *fuego.BaseRoute) {
    	r.XXX = YYY // Direct access to the route struct
    }
    
    func main() {
    	s := fuego.NewServer()
    	api := fuego.Group(s, "/users",
    		option.Summary("Users routes"),
    		option.Tags("users"),
    	)
    
    	fuego.Get(api, "/", helloWorld,
    		optionPagination,
    		customOption,
    		option.Summary("A specific summary for this route"),
    	)
    	s.Run()
    }
  6. Create custom error types with ErrorWithStatus

    main

    To return custom error structures that Fuego can automatically transform into RFC 9457 compliant HTTP responses, implement the fuego.ErrorWithStatus interface. This interface requires an Error() string method and a StatusCode() int method.

    When an error implements fuego.ErrorWithStatus, Fuego transforms it into a fuego.HTTPError. The resulting response format (JSON or XML) is determined by the client's Accept header.

    type ErrorWithStatus interface {
    	error
    	StatusCode() int
    }
  7. Understand Fuego's core architecture components

    main

    Fuego is built around four primary components that manage the lifecycle of a request and the structure of your application:

    • Engine: The core of Fuego. It manages the request/response lifecycle, holds the OpenAPI struct (including descriptions and OpenAPI utilities), and provides a centralized Error Handler.
    • Server: The underlying net/http server used to listen for requests. It manages routing, route groups, and middleware.
    • Adaptors: Integration layers that allow you to use Fuego with other web frameworks like Gin or Echo.
    • Context: A generic-typed interface used within controllers. It represents the state that a user can access and modify during a request.
  8. Inherit route options via Server level configuration

    main

    You can pass route options to fuego.NewServer using fuego.WithRouteOptions. These options will be inherited by every route in the server.

    Note: The maintainers recommend using option.Group instead of server-level inheritance for a more composable approach.

    package main
    
    import (
    	"github.com/go-fuego/fuego"
    	"github.com/go-fuego/fuego/option"
    )
    
    func main() {
    	s := fuego.NewServer(
    		fuego.WithRouteOptions(
    			option.Summary("Pets operations"),
    			option.Description("Operations about pets"),
    			option.Tags("pets"),
    		),
    	)
    
    	fuego.Get(s, "/", func(c fuego.ContextNoBody) (string, error) {
    		return "Hello, World!", nil
    	})
    }
  9. Understand Fuego controller types

    main

    Controllers are the primary way to handle requests and responses in Fuego. A controller is a function that receives a context and returns a response and an error. Fuego supports Content Negotiation, allowing a single controller to serve different content types based on the Accept header.

    Fuego avoids reflection for high performance, relying instead on underlying libraries like net/http, encoding/json, or gin (if using fuegogin) for deserialization.

    // Standard fuego controller
    func MyController(c fuego.ContextWithBody[Body]) (MyResponse, error)
  10. Transform data with InTransform and OutTransform

    main

    You can implement the fuego.InTransform and fuego.OutTransform interfaces on your request/response structs to perform custom data manipulation or validation.

    • InTransform(context.Context) error: Called just before c.Body() returns the data. Use this to sanitize inputs (e.g., lowercase strings) or perform complex validation. If it returns an error, the request fails.
    • OutTransform(context.Context) error: Called before the response is sent to the client. Use this to transform outgoing data (e.g., uppercase strings).
    type MyInput struct {
    	Name string `json:"name" validate:"required"`
    }
    
    // Will be called just before returning c.Body()
    func (r *MyInput) InTransform(context.Context) error {
    	r.Name = strings.ToLower(r.Name)
    
    	if r.Name == "fuego" {
    		return errors.New("fuego is not a valid name for this input")
    	}
    
    	return nil
    }
  11. Understand the Fuego Transformation and Validation Flow

    main

    The lifecycle of a request and response in Fuego follows this specific sequence:

    1. Request Arrival: Payload (JSON/XML/etc.) is received.
    2. Deserialization: Payload is unmarshaled into your request struct.
    3. Input Transformation: InTransform is called on the struct (if implemented).
    4. Validation: Standard validation is performed based on struct tags.
    5. Controller Execution: Your controller is called with the transformed/validated struct.
    6. Response Return: Your controller returns a response struct.
    7. Output Transformation: OutTransform is called on the response struct (if implemented).
    8. Serialization: Response struct is serialized and sent to the client.
  12. Default error handling in Fuego

    main

    Fuego controllers return a value and an error. If a controller returns a non-nil error, Fuego automatically handles it:

    1. Standard errors: If you return a generic error (e.g., errors.New("...")), Fuego returns a 500 Internal Server Error to the client. The error message is logged to the console but is not returned to the client because it is not serializable.
    2. Structured errors: Fuego provides built-in error types that implement RFC 9457. Using these types allows you to return structured JSON or XML responses with appropriate HTTP status codes.

    Recommended built-in error types:

    • fuego.BadRequestError: 400 Bad Request
    • fuego.UnauthorizedError: 401 Unauthorized
    • fuego.ForbiddenError: 403 Forbidden
    • fuego.NotFoundError: 404 Not Found
    • fuego.NotAcceptableError: 406 Not Acceptable
    • fuego.ConflictError: 409 Conflict
    • fuego.InternalServerError: 500 Internal Server Error
    func MyController(c fuego.ContextNoBody) (string, error) {
    	_, err := someFunction()
    	if err != nil {
    		return "", fuego.BadRequestError{Title: "You cannot do that", Err: err} // Returns and logs a structured 400 error.
    	}
    
    	return "success", nil
    }