Documentation
¶
Overview ¶
Package xapi provides a type-safe lightweight HTTP API framework for Go.
Most HTTP handlers follow the same pattern - decode JSON, extract headers/params, validate, call business logic, encode response. xapi codifies that pattern using generics, so you write less but get more type safety. Your request and response types define the API contract. The optional interfaces provide flexibility when needed.
The result: handlers that are mostly business logic, with HTTP operations abstracted away into a lightweight framework. You can use it with your existing HTTP router and server, keeping all existing middlewares and error handling.
Core Types ¶
Endpoint is the main type that wraps your EndpointHandler and applies middleware and error handling. Create endpoints using NewEndpoint with your handler and optional configuration via EndpointOption values.
EndpointFunc is a function type that implements EndpointHandler, providing a convenient way to create handlers from functions.
Optional Interfaces ¶
xapi defines four optional interfaces. Implement them on request and response types only when needed:
Validator runs after JSON decoding to validate the request. You can use any validation library here.
Extracter pulls data from the HTTP request that isn't in the JSON body, such as headers, route path params, or query strings.
StatusSetter controls the HTTP status code. The default is 200, but you can override it to return 201 for creation, 204 for no content, etc.
RawWriter bypasses JSON encoding entirely for HTML, or binary responses. Use this when you need full control over the response format.
Middleware ¶
Middleware works exactly like standard http.Handler middleware. Any middleware you're already using will work. Stack them in the order you need using WithMiddleware. They wrap the endpoint cleanly, keeping auth, logging, and metrics separate from your business logic. Use MiddlewareFunc to convert functions to middleware, or implement MiddlewareHandler for custom middleware types.
Error Handling ¶
Default behavior is a 500 status with the error text. Customize this using WithErrorHandler to distinguish validation errors from auth failures, map them to appropriate status codes, and format them consistently. Implement the ErrorHandler interface or use ErrorFunc for simple function-based handlers. The default error handling is provided by DefaultErrorHandler.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultErrorHandler ¶
func DefaultErrorHandler(w http.ResponseWriter, err error)
DefaultErrorHandler provides default error handling for common JSON errors.
Types ¶
type Endpoint ¶
type Endpoint[TReq, TRes any] struct { // contains filtered or unexported fields }
Endpoint represents a type-safe HTTP endpoint with middleware and error handling.
Example (Basic) ¶
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/gojekfarm/xtools/xapi"
)
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Language string `json:"-"`
}
func (user *CreateUserRequest) Validate() error {
if user.Name == "" {
return fmt.Errorf("name is required")
}
if user.Email == "" {
return fmt.Errorf("email is required")
}
return nil
}
func (user *CreateUserRequest) Extract(r *http.Request) error {
user.Language = r.Header.Get("Language")
return nil
}
type CreateUserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Language string `json:"language"`
}
func (user *CreateUserResponse) StatusCode() int {
return http.StatusCreated
}
func main() {
createUser := xapi.EndpointFunc[CreateUserRequest, CreateUserResponse](
func(ctx context.Context, req *CreateUserRequest) (*CreateUserResponse, error) {
// Simulate user creation logic
return &CreateUserResponse{
ID: 1,
Name: req.Name,
Email: req.Email,
Language: req.Language,
}, nil
},
)
endpoint := xapi.NewEndpoint(createUser)
http.Handle("/users", endpoint.Handler())
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Example (CustomErrorHandler) ¶
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/gojekfarm/xtools/xapi"
)
func main() {
type GetUserRequest struct {
ID int `json:"id"`
}
type GetUserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
customErrorHandler := xapi.ErrorFunc(func(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
errorResponse := map[string]string{
"error": err.Error(),
}
json.NewEncoder(w).Encode(errorResponse)
})
getUser := xapi.EndpointFunc[GetUserRequest, GetUserResponse](
func(ctx context.Context, req *GetUserRequest) (*GetUserResponse, error) {
if req.ID <= 0 {
return nil, fmt.Errorf("invalid user ID: %d", req.ID)
}
// Simulate user lookup
return &GetUserResponse{
ID: req.ID,
Name: "John Doe",
Email: "john@example.com",
}, nil
},
)
// Create endpoint with custom error handler
endpoint := xapi.NewEndpoint(
getUser,
xapi.WithErrorHandler(customErrorHandler),
)
http.Handle("/users/", endpoint.Handler())
}
Example (WithCustomResponseWriter) ¶
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/gojekfarm/xtools/xapi"
)
type GetArticleRequest struct {
ID string `json:"-"`
}
func (article *GetArticleRequest) Extract(r *http.Request) error {
article.ID = r.PathValue("id")
return nil
}
type GetArticleResponse struct {
ID string `json:"id"`
Title string `json:"title"`
}
func (article *GetArticleResponse) Write(w http.ResponseWriter) error {
w.Header().Set("Content-Type", "application/html")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, "<html><body><h1>%s</h1></body></html>", article.Title)
return nil
}
func main() {
getArticle := xapi.EndpointFunc[GetArticleRequest, GetArticleResponse](
func(ctx context.Context, req *GetArticleRequest) (*GetArticleResponse, error) {
return &GetArticleResponse{
ID: req.ID,
Title: "Article " + req.ID,
}, nil
},
)
endpoint := xapi.NewEndpoint(getArticle)
http.Handle("/articles/{id}", endpoint.Handler())
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Example (WithMiddleware) ¶
package main
import (
"context"
"fmt"
"log"
"net/http"
"github.com/gojekfarm/xtools/xapi"
)
func main() {
type GetDataRequest struct {
ID string `json:"id"`
}
type GetDataResponse struct {
ID string `json:"id"`
Data string `json:"data"`
}
// Authentication middleware
authMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Simulate token validation
if token != "Bearer valid-token" {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// Rate limiting middleware
rateLimitMiddleware := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simulate rate limiting logic
w.Header().Set("X-RateLimit-Limit", "100")
w.Header().Set("X-RateLimit-Remaining", "99")
next.ServeHTTP(w, r)
})
}
getData := xapi.EndpointFunc[GetDataRequest, GetDataResponse](
func(ctx context.Context, req *GetDataRequest) (*GetDataResponse, error) {
requestID := ctx.Value("requestID")
log.Printf("Processing request %s for ID: %s", requestID, req.ID)
return &GetDataResponse{
ID: req.ID,
Data: fmt.Sprintf("Data for %s", req.ID),
}, nil
},
)
endpoint := xapi.NewEndpoint(
getData,
xapi.WithMiddleware(
xapi.MiddlewareFunc(rateLimitMiddleware),
xapi.MiddlewareFunc(authMiddleware),
),
)
http.Handle("/data", endpoint.Handler())
}
func NewEndpoint ¶
func NewEndpoint[TReq, TRes any](handler EndpointHandler[TReq, TRes], opts ...EndpointOption) *Endpoint[TReq, TRes]
NewEndpoint creates a new Endpoint with the given handler and options.
type EndpointFunc ¶
EndpointFunc is a function type that implements EndpointHandler.
type EndpointHandler ¶
type EndpointHandler[TReq, TRes any] interface { Handle(ctx context.Context, req *TReq) (*TRes, error) }
EndpointHandler defines the interface for handling endpoint requests.
type EndpointOption ¶
type EndpointOption interface {
// contains filtered or unexported methods
}
EndpointOption defines the interface for endpoint configuration options.
func WithErrorHandler ¶
func WithErrorHandler(errorHandler ErrorHandler) EndpointOption
WithErrorHandler returns an EndpointOption that sets a custom error handler for the endpoint.
func WithMiddleware ¶
func WithMiddleware(middlewares ...MiddlewareHandler) EndpointOption
WithMiddleware returns an EndpointOption that adds middleware to the endpoint.
type ErrorFunc ¶
type ErrorFunc func(w http.ResponseWriter, err error)
ErrorFunc is a function type that implements ErrorHandler.
func (ErrorFunc) HandleError ¶
func (e ErrorFunc) HandleError(w http.ResponseWriter, err error)
HandleError implements the ErrorHandler interface.
type ErrorHandler ¶
type ErrorHandler interface {
HandleError(w http.ResponseWriter, err error)
}
ErrorHandler defines the interface for handling errors in HTTP responses.
type Extracter ¶
Extracter allows extracting additional data from the HTTP request, such as headers, query params, etc.
type MiddlewareFunc ¶
MiddlewareFunc is a function type that implements MiddlewareHandler.
func (MiddlewareFunc) Middleware ¶
func (m MiddlewareFunc) Middleware(next http.Handler) http.Handler
Middleware implements the MiddlewareHandler interface.
type MiddlewareHandler ¶
MiddlewareHandler defines the interface for HTTP middleware.
type MiddlewareStack ¶
type MiddlewareStack []MiddlewareHandler
MiddlewareStack represents a stack of middleware handlers.
func (MiddlewareStack) Middleware ¶
func (m MiddlewareStack) Middleware(next http.Handler) http.Handler
Middleware applies all middleware in the stack to the given handler. Middleware is applied in reverse order, so the last added middleware wraps the innermost handler.
type RawWriter ¶
type RawWriter interface {
Write(w http.ResponseWriter) error
}
RawWriter allows writing raw data to the HTTP response instead of the default JSON encoder.
type StatusSetter ¶
type StatusSetter interface {
StatusCode() int
}
StatusSetter allows setting a custom HTTP status code for the response.