request

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: MIT Imports: 5 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 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