xapi

package module
v0.11.0-alpha.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2025 License: MIT Imports: 4 Imported by: 0

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.

func (*Endpoint[TReq, TRes]) Handler

func (e *Endpoint[TReq, TRes]) Handler() http.Handler

Handler returns an http.Handler that processes requests for this endpoint.

type EndpointFunc

type EndpointFunc[TReq, TRes any] func(ctx context.Context, req *TReq) (*TRes, error)

EndpointFunc is a function type that implements EndpointHandler.

func (EndpointFunc[TReq, TRes]) Handle

func (e EndpointFunc[TReq, TRes]) Handle(ctx context.Context, req *TReq) (*TRes, error)

Handle implements the EndpointHandler interface.

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

type Extracter interface {
	Extract(r *http.Request) error
}

Extracter allows extracting additional data from the HTTP request, such as headers, query params, etc.

type MiddlewareFunc

type MiddlewareFunc func(next http.Handler) http.Handler

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

type MiddlewareHandler interface {
	Middleware(next http.Handler) http.Handler
}

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.

type Validator

type Validator interface {
	Validate() error
}

Validator allows validating endpoint requests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL