Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📝 [Proposal]: Adding "API Schemas" Support in Fiber Framework (V3) #3271

Open
3 tasks done
xehrad opened this issue Jan 2, 2025 · 3 comments
Open
3 tasks done

Comments

@xehrad
Copy link

xehrad commented Jan 2, 2025

Feature Proposal Description

This proposal introduces support for "API Schemas" in the Fiber framework, inspired by the implementation in [Encore](https://encore.dev/docs/go/primitives/defining-apis). API Schemas allow developers to define HTTP APIs declaratively using Go structs, enabling seamless type safety, validation, and automated documentation generation.

Key benefits of API Schemas include:

  1. Type-Safe API Definitions: Ensures compile-time safety by leveraging Go’s type system to define endpoints, request bodies, and response structures.
  2. Validation and Consistency: Enforces schema-based validation for request and response data, improving API robustness.
  3. Ease of Use: Simplifies the process of creating, maintaining, and evolving APIs in Fiber applications.

By adopting this feature, Fiber can elevate its usability for developers building modern, scalable REST APIs, while ensuring best practices for API design.

Conclusion

Adding support for API Schemas in Fiber V3 will greatly enhance the framework’s functionality, enabling developers to define APIs in a structured, type-safe, and declarative manner. This feature aligns Fiber with modern API development practices, bridging the gap between simplicity and scalability while ensuring adherence to HTTP standards and long-term stability.

Let’s make Fiber V3 the go-to framework for robust Go-based API development! ;P

Alignment with Express API

The addition of API Schemas aligns Fiber closer to Express.js-like API frameworks. Fiber has traditionally emphasized high performance and simplicity, but Express’s ecosystem thrives due to middleware and declarative features like JSON Schema validations (via libraries). API Schemas in Fiber would natively close this gap, allowing for similar or enhanced developer experiences in Go.

HTTP RFC Standards Compliance

API Schemas inherently adhere to HTTP RFC standards by standardizing request and response structures based on predefined schemas. This ensures that:

  • HTTP methods (GET, POST, etc.) and status codes align with defined API behaviors.
  • Headers, query parameters, and bodies comply with schema definitions, enabling better compatibility and client-server integration.

Including such standards compliance natively in Fiber will boost its adoption for enterprise-grade applications requiring strict adherence to HTTP specifications.

API Stability

Integrating API Schemas would also provide a clear foundation for API versioning and backward compatibility:

  • Versioned Schemas: Developers can define multiple schemas for different versions of an API, ensuring smooth migrations.
  • Static Analysis: Compile-time type checking reduces runtime errors and ensures that API changes are predictable.
  • Schema Validation Tools: Fiber could include built-in or pluggable tools for validating API stability during development and deployment.

By focusing on these aspects, API Schemas in Fiber will promote stable and reliable APIs that are future-proof and maintainable.

Feature Examples

// PingParams is the request data for the Ping endpoint.
type PingParams struct {
	Header  string `f_header:"X-Header"` // this field will be read from the http header
	Query   string `f_query:"q"`         // this field will be read from the query string
	Company string `f_url:"company"`     // this field will be read from the URL path
	ID      string `f_url:"id"`
	Name    string `json:"name"`
}

// PingResponse is the response data for the Ping endpoint.
type PingResponse struct {
	Message string
}

// Ping is an API endpoint that responds with a simple response.
// This is exposed as "/project/:company/:id?q=foo".
//
//fiber:api method=POST path=/project/:company/:id
func Ping(c *fiber.Ctx, params *PingParams) (*PingResponse, error) {
	msg := fmt.Sprintf("hello %s", params.Name)
	return &PingResponse{Message: msg}, nil
}

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have searched for existing issues that describe my proposal before opening this one.
  • I understand that a proposal that does not meet these guidelines may be closed without explanation.
@pwtitle
Copy link

pwtitle commented Jan 4, 2025

good!

@nickajacks1
Copy link
Member

I see two main pieces to this proposal.

  1. Adding handler functions that take the request body as a struct parameter and return a struct as the response body.
  2. Adding codegen as a means of implementing the above

Right off the bat, I feel this is a rather large deviation from the current direction of v3. I'd imagine if something of this nature was to be considered, we might want to think of it in the context of v4 or beyond. In terms of implementation, it seems like it would be part of or an extension of the Bind API. In fact, perhaps something like this could fit in as an addon of some kind. If implemented as an addon that wraps regular routes somehow, perhaps it could still be implemented at any time post v3 release.

@xehrad
Copy link
Author

xehrad commented Jan 27, 2025

Hi, Thank you for your feedback. I want to clarify that the proposal does not intend to change the structure of handler functions. The idea is not to modify how handlers currently work. Instead, it involves using code generation to automatically create a handler function based on the metadata in the struct.

The flow would remain the same: we would generate a function that reads from the struct using the current method of binding (e.g., f_query, f_header, f_url), calls the user-defined handler function, and then converts the resulting struct to JSON before sending it as the response. So, there would be no change to the core handler structure itself.

The proposal focuses on generating handler functions using the schema definitions, which can improve usability and maintain type safety, validation, and documentation, without altering the existing approach to how handlers are executed.

// Generated  function that reads from the struct 
func PingHandler(c *fiber.Ctx) error {
	// Extract data from the request
	req := new(PingParams)

	// Parse JSON body into struct
	if err := c.BodyParser(req); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString("Invalid request body")
	}

	// From Header
	req.Header = c.Get("X-Header")

	// From URL Query
	req.Query = c.Query("q")

	// From URL Path Params
	req.Company = c.Params("company")
	req.ID = c.Params("id")

	// Calls the user-defined function
	res := Ping(c, req)

	// Respond with converts the resulting struct
	return c.JSON(res)
}


func main() {
	app := fiber.New()
	app.Post("/project/:company/:id", PingHandler)
	app.Listen(":3000")
}

Let me know if this clears things up!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants