request

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 7 Imported by: 0

README

lazylib/request

CI Go Reference Go Report Card Go Version

A small, dependency-free Go toolkit for REST/JSON HTTP APIs. Typed Send and SendX helpers, pluggable auth, raw-body support — composed from focused building blocks rather than a mega-client.

result, err := request.Send[User](request.Options{
    Method:  http.MethodGet,
    Url:     "https://api.example.com/users/42",
    Headers: map[string]string{"Accept": "application/json"},
    Auth:    request.BearerAuth{Token: token},
})
if err != nil {
    return err
}
fmt.Println(result.Name)

Send for the err-returning case, SendX for the panic-on-failure case. Same Options, same JSON handling, same auth — pick the one that fits the call site.


Why?

Every Go project that calls a JSON HTTP API eventually rewrites the same helper: build a request, set headers, marshal the body, send it, check the status, decode the response, return the error. lazylib/request ships that helper — and a few siblings — battle-tested and zero-dependency, so you can stop writing it again.

What you want What you write
GET JSON, decode into struct request.Send[T](Options{Method: GET, Url: …})
Same as above, but panic on failure request.SendX[T](Options{…})
POST struct as JSON pass Body: myStruct
POST raw bytes / stream pass Body: []byte or io.Reader
HTTP Basic Auth: &BasicAuth{User, Pass}
Bearer token Auth: BearerAuth{Token: …}
Custom auth scheme implement the Auth interface (2 lines)
Custom headers Headers: map[string]string{…}
Custom client / timeout / retries see When NOT to use

Install

go get github.com/lazylib/request

Requires Go 1.22+ (uses generics).

Quick start

package main

import (
    "fmt"
    "net/http"

    "github.com/lazylib/request"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    u, err := request.Send[User](request.Options{
        Method: http.MethodGet,
        Url:    "https://api.example.com/users/1",
        Auth:   request.BearerAuth{Token: "secret"},
    })
    if err != nil {
        panic(err)
    }
    fmt.Println(u.Name)
}

POST a JSON body, decode the JSON response:

type CreateUserReq struct {
    Name string `json:"name"`
}
type CreateUserResp struct {
    ID int `json:"id"`
}

resp, err := request.Send[CreateUserResp](request.Options{
    Method: http.MethodPost,
    Url:    "https://api.example.com/users",
    Body:   CreateUserReq{Name: "Alice"},
    Auth:   request.BearerAuth{Token: "secret"},
})

POST raw bytes (e.g. pre-signed payload, file upload, webhook body):

_, err := request.Send[struct{}](request.Options{
    Method: http.MethodPost,
    Url:    "https://api.example.com/hooks/42",
    Body:   []byte(`{"event":"ping"}`),
})

Send a request with no body and no response body (e.g. 204 No Content):

_, err := request.Send[struct{}](request.Options{
    Method: http.MethodDelete,
    Url:    "https://api.example.com/users/1",
    Auth:   request.BearerAuth{Token: "secret"},
})

HTTP Basic auth (e.g. payment gateways like YooKassa, Cloudflare R2, internal APIs):

resp, err := request.Send[Payment](request.Options{
    Method: http.MethodPost,
    Url:    "https://api.yookassa.ru/v3/payments",
    Body:   payload,
    Auth:   &request.BasicAuth{Username: "shop-id", Password: "secret"},
})

API

Send[T any](opts Options) (*T, error)

Performs the request and decodes the response body into *T.

Returns an error if:

  • the request cannot be built or sent (network error, invalid URL)
  • the server replies with a non-2xx status — error message includes the status code
  • the response body cannot be decoded as JSON
  • for 204 No Content / empty bodies, returns (nil, nil) — no need to special-case
SendX[T any](opts Options) *T

The panicking variant of Send. Calls Send[T] and panics with the returned error if it is non-nil. Useful when an HTTP failure is a programmer error or process-fatal condition (similar to regexp.MustCompile or template.Must).

config := request.SendX[Config](request.Options{
    Method: http.MethodGet,
    Url:    "https://api.example.com/config",
    Auth:   request.BearerAuth{Token: token},
})
// config is *Config, never nil, no error to check.

For most code paths, prefer Send and return the error.

Options
type Options struct {
    Method  string             // "GET", "POST", ...
    Url     string             // absolute URL
    Body    any                // nil | struct/map | []byte | *bytes.Buffer | *bytes.Reader | io.Reader | string
    Headers map[string]string  // optional, merged on top of Content-Type for JSON bodies
    Auth    Auth               // optional, BasicAuth / BearerAuth / your own
    Client  *http.Client       // custom client
}

Body handling:

Body type Sent as Content-Type set
nil (empty) (none)
[]byte raw (none)
*bytes.Buffer/*bytes.Reader raw (none)
io.Reader raw (none)
string raw (none)
anything else json.Marshal(body) application/json

If you set Headers["Content-Type"], it overrides the automatic value.

Auth interface
type Auth interface {
    apply(*http.Request) // unexported
}

Implement this interface to plug in any auth scheme (HMAC, OAuth1, mTLS, signature v4, …):

type MyAuth struct{ Key, Secret string }

func (a MyAuth) apply(r *http.Request) {
    r.Header.Set("X-Api-Key", a.Key)
    // …sign the request however you like
}

// Auth: MyAuth{Key: "k", Secret: "s"}

Built-in helpers:

  • BasicAuth{Username, Password string} — Authorization: Basic …
  • BearerAuth{Token string} — Authorization: Bearer …

Comparison

vs. stdlib net/http
// stdlib — 10+ lines, easy to get wrong
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("status %d", resp.StatusCode) }
var out MyResponse
return json.NewDecoder(resp.Body).Decode(&out)
// this package — 1 call
return request.Send[MyResponse](request.Options{
    Method: http.MethodPost, Url: url, Body: body,
    Auth: request.BearerAuth{Token: token},
})
vs. net/http (Go 1.22+ has http.NewRequestWithContext — still verbose)

This package sits on top of net/http. It does not replace it. For complex needs (timeouts, retries, middleware, connection pooling), use net/http directly or a heavier client like hashicorp/go-retryablehttp.

vs. resty, gentleman, req, gorequest

Those are full HTTP clients with retries, middleware, and chainable builders. This package is deliberately tiny — one function, one struct, two auth types, zero dependencies. Use it when you want a stdlib-feeling helper, not a framework.

When NOT to use

You probably want net/http or hashicorp/go-retryablehttp instead if you need:

  • retries with backoff
  • per-request timeouts and a shared *http.Client
  • request/response middleware, tracing, metrics
  • OAuth2 token refresh flows
  • multipart form uploads, streaming downloads
  • WebSockets / SSE

Examples

Runnable examples live in ./examples. They cover:

Project status

Active. The toolkit grows by adding small, focused helpers on top of the same Options and Auth primitives — Send and SendX today, more building blocks over time. The public surface is designed to be backwards-compatible across minor versions: new helpers are additive, and existing call sites are not expected to change.

Contributing

Bug reports and PRs welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md. Run go test ./... and go vet ./... before submitting.

License

MIT.

Documentation

Overview

Package request is a small, dependency-free Go toolkit for working with REST/JSON HTTP APIs. It grows by adding focused, composable helpers rather than shipping a single mega-client.

The current public surface is intentionally small:

  • Send — generic JSON in / typed Go out for any HTTP call.
  • SendX — the panicking variant of Send, for cases where a non-2xx response is a programmer error or process-fatal condition.
  • Options, Auth, BasicAuth, BearerAuth — the configuration types both helpers build on.

A typical call looks like:

result, err := request.Send[MyResponse](request.Options{
    Method: http.MethodPost,
    Url:    "https://api.example.com/things",
    Body:   MyRequest{Name: "hello"},
    Headers: map[string]string{"X-Trace": "abc"},
    Auth:   request.BearerAuth{Token: token},
})

JSON encoding of the body, JSON decoding of the response, non-2xx status codes, raw []byte / io.Reader payloads, and pluggable auth schemes are all handled by Send. The package has no third-party dependencies and works on Go 1.22+ (requires generics).

When to use this package

Use it when stdlib net/http feels verbose and you want typed JSON responses without a full client (retries, connection pooling, middleware). Send covers "JSON in, typed Go out"; SendX covers the same case in a panic-on-failure form.

When NOT to use this package

If you need retries, timeouts, circuit breakers, tracing, automatic rate-limit handling, or a pre-configured HTTP client, reach for net/http directly or a library such as hashicorp/go-retryablehttp.

Package request provides a small generic helper for sending HTTP requests and decoding JSON responses.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Download added in v0.2.2

func Download(opts Options, to string) error

Download performs opts and writes the response body to the file path specified by `to`. It returns an error if the request cannot be built or sent, if the server responds with a non-2xx status, or if writing the file fails.

func DownloadX added in v0.2.2

func DownloadX(opts Options, to string)

DownloadX performs the same operation as Download but panics on error.

func Send

func Send[T any](opts Options) (*T, error)

Send performs opts and decodes the response body into *T.

It returns an error if the request cannot be built or sent, if the server replies with a non-2xx status, or if the response body cannot be decoded as JSON.

func SendX added in v0.2.0

func SendX[T any](opts Options) *T

SendX is the panicking variant of Send.

It calls Send[T] and panics with the returned error if it is non-nil. It is meant for cases where an HTTP failure is a programmer error or a process-fatal condition (similar to entgo.io/ent's Must helpers, or regexp.MustCompile): the request is so fundamental to the program's flow that handling the error would only obscure control flow.

For most code paths, prefer Send and return the error.

Example:

user := request.SendX[User](request.Options{
    Method: http.MethodGet,
    Url:    "https://api.example.com/users/1",
    Auth:   request.BearerAuth{Token: "secret"},
})
fmt.Println(user.Name) // no error to check

Types

type Auth added in v0.1.1

type Auth interface {
	// contains filtered or unexported methods
}

Auth is implemented by any value that can apply credentials to an outgoing http.Request (e.g. BasicAuth).

type BasicAuth added in v0.1.1

type BasicAuth struct {
	Username string
	Password string
}

BasicAuth adds HTTP Basic credentials to the request.

type BearerAuth added in v0.1.1

type BearerAuth struct {
	Token string
}

BearerAuth adds an Authorization: Bearer <token> header to the request.

type Options added in v0.1.1

type Options struct {
	Method  string
	Url     string
	Body    any
	Headers map[string]string
	Auth    Auth
	Client  *http.Client
}

Options describes a single HTTP request.

Body can be any value that json.Marshal can handle. If it is nil, an empty request body is sent. If it is already a []byte or io.Reader, it is used as-is (without re-encoding).

Headers is merged on top of the Content-Type that the package sets for JSON bodies, so callers can override it.

Jump to

Keyboard shortcuts

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