Backend Development 10 min read

Introducing Hasaki: A Simple Golang HTTP Restful Client with Custom Decoders and Middleware

This article describes the motivation, design, and usage of Hasaki, a lightweight Go HTTP Restful client that supports custom status‑code handling, multiple data formats such as JSON, XML, YAML and protobuf, builder‑style chaining, and extensible middleware, providing code examples and a plugin architecture.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Introducing Hasaki: A Simple Golang HTTP Restful Client with Custom Decoders and Middleware

I am Lee, a veteran with 17 years of experience in the IT industry, and I often use Restful client tools like go‑resty to call APIs.

Recent business changes require the client to handle non‑200 status codes (e.g., 400, 401, 403, 404, 500) and various response formats (JSON, XML, YAML, protobuf), which go‑resty cannot satisfy because it only supports JSON/XML decoding and invokes SetResult only for 200‑299 responses.

The required features therefore include custom decoders for formats such as protobuf, support for arbitrary status codes, and a builder‑style API that allows fluent chaining without deep nesting.

To address these needs I joined the open‑source project Hasaki (https://github.com/lxzan/hasaki), a Golang Restful client implementation that offers a thin wrapper around net/http while providing clearer code and easier maintenance.

Basic usage is straightforward; for example:

type Req struct {
    Q    string `json:"q"`
    Page int    `json:"page"`
}

type Resp struct {
    Total int `json:"total"`
}

data := Resp{}
err := hasaki.
    Post("https://api.example.com/search").
    Send(&Req{Q: "golang", Page: 1}).
    BindJSON(&data)
if err != nil {
    log.Printf("%+v", err)
    return
}
log.Printf("%+v", data)

When using Bind or BindJSON , Hasaki currently cannot expose the response status code without breaking the chain, which is a usability issue that could be improved.

The library also provides middleware hooks: WithBefore runs before a request, and WithAfter runs after a response, enabling custom logic such as latency measurement.

before := hasaki.WithBefore(func(ctx context.Context, request *http.Request) (context.Context, error) {
    return context.WithValue(ctx, "t0", time.Now()), nil
})

after := hasaki.WithAfter(func(ctx context.Context, response *http.Response) (context.Context, error) {
    t0 := ctx.Value("t0").(time.Time)
    log.Printf("latency=%s", time.Since(t0).String())
    return ctx, nil
})

cli, _ := hasaki.NewClient(before, after)
cli.Get("https://api.github.com/search/repositories").Send(nil)

For extensibility, encoders/decoders can be added as plugins. The article shows a simple YAML encoder/decoder implementation that registers with Hasaki, demonstrating how any format can be supported without modifying the core library.

package yaml

import (
    "bytes"
    "github.com/lxzan/hasaki"
    "github.com/lxzan/hasaki/internal"
    "github.com/pkg/errors"
    "github.com/valyala/bytebufferpool"
    "gopkg.in/yaml.v3"
    "io"
)

var Encoder = new(encoder)

type encoder struct{}

func (c encoder) Encode(v any) (io.Reader, error) {
    if v == nil {
        return nil, nil
    }
    w := bytebufferpool.Get()
    err := yaml.NewEncoder(w).Encode(v)
    r := &internal.CloserWrapper{B: w, R: bytes.NewReader(w.B)}
    return r, errors.WithStack(err)
}

func (c encoder) ContentType() string { return hasaki.MimeYaml }

func Decode(r io.Reader, v any) error { return errors.WithStack(yaml.NewDecoder(r).Decode(v)) }

In summary, Hasaki offers a clean, minimalistic HTTP client for Go that supports custom decoding, flexible status‑code handling, and extensible middleware, making it a practical alternative to more heavyweight libraries; contributors are encouraged to submit issues or pull requests.

middlewareGoProtobufRESTfulcustom decoderHTTP ClientHasaki
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.