Library / SDK
earthboundkid/requests avatar
earthboundkid/requests

requests for Go: a fluent Builder over net/http that closes the body for you

HTTP requests for Gophers

1,671 stars63 forksGoMIT

At a glance

What is it?
The requests package wraps Go's net/http in a fluent Builder where every method returns the same pointer, so a request is a chain ending in Fetch. It checks status codes by default and always takes a context, but its module path is carlmjohnson/requests while the repository is earthboundkid/requests.
Who is it for?
Adopt requests if your Go code makes many outbound HTTP calls and you keep losing lines to body closing, status checks and context plumbing, since the Builder removes all three by construction rather than by convention. Do not adopt it to avoid net/http, because every request still goes through http.Client and you should be comfortable with that client anyway.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Builder where every method returns the same pointer

The mechanism is a single struct with a fluent API. The README describes requests.Builder as a convenient way to build, send and handle HTTP requests, with methods returning a pointer to the same struct, which allows a request to be described declaratively by method chaining.

That design choice explains everything else about the package. Since each call mutates and returns the same value, there is no intermediate request object to lose track of and no way to construct one partially.

The comparison the README puts side by side is the clearest statement of intent. A GET into a string is 11 or more lines with net/http: build the request, check the error, send it, check the error, defer the close, read the body, check the error, convert to string.

go
var s string
err := requests.
	URL("http://example.com").
	ToString(&s).
	Fetch(ctx)

Five lines against eleven, and the reduction is not cosmetic. The four lines that disappear are exactly the four that are easy to forget.

A JSON GET goes from 18 or more lines to 7, and the path is built with a format helper rather than by parsing a URL and reassigning its Path.

go
var post placeholder
err := requests.
	URL("https://jsonplaceholder.typicode.com").
	Pathf("/posts/%d", 1).
	ToJSON(&post).
	Fetch(ctx)

The POST example splits host from path so the same builder can move between environments, which is the URL manipulation feature in miniature.

Four defaults that change error handling

The feature list is short, and four items on it are behavioural defaults rather than conveniences.

You cannot forget to close the response body. The builder closes it for you, which is the single most common bug in hand-written Go HTTP clients.

Status codes are checked by default. This is the item with the most design consequence: a 404 or a 500 becomes an error rather than a response you have to inspect. If you want a non-2xx response returned to your code instead of turned into an error, the validators exist to override it, and the repository has a validator.go, validator_handler.go and matching example tests.

A context.Context is always required. Every Fetch takes a ctx, which is what makes the package compatible with the cancellation and deadlines your service already uses.

JSON serialization and deserialization are handled through BodyJSON and ToJSON, so the encoding and decoding steps that pad out net/http examples become two chain links.

The README justifies the first two by pointing at Brad Fitzpatrick's list of problems with the standard library client, described as long time maintainer of net/http. His four main points, filtered to those a wrapper can fix, are that it is too easy to not call Response.Body.Close, too easy to not check return status codes, that context support is oddly bolted on, and that proper usage takes too many lines of boilerplate. Requests claims all four.

Recording to the filesystem, then replaying in tests

Test tooling is part of the package rather than a separate module, in reqtest/. The mechanism is a transport, so nothing about the request changes between recording and replay.

go
// record a request to the file system
var s1, s2 string
err := requests.URL("http://example.com").
	Transport(reqtest.Record(nil, "somedir")).
	ToString(&s1).
	Fetch(ctx)
check(err)

// now replay the request in tests
err = requests.URL("http://example.com").
	Transport(reqtest.Replay("somedir")).
	ToString(&s2).
	Fetch(ctx)
check(err)
assert(s1 == s2) // true

Record takes a body argument and a directory, writes the exchange there, and Replay takes the directory and serves it back. Because both are installed with Transport, your production code never branches on whether it is under test.

That design also covers the complaint the README makes about other helper libraries: many hide the underlying http.Client in a way that makes it difficult or impossible to replace or mock. Here the transport is an explicit link in the chain, and the README notes that custom transports and validators are compatible with the standard library and with third party libraries.

The repository root shows the rest of the surface as separate files, core_do.go, core_req.go, core_url.go, transport.go, validator.go, redirects.go, cookies.go, body.go, serializer.go, handler.go, recorder.go, config.go and errorkind.go, with example tests alongside most of them. The subpackages reqhtml/, reqxml/ and reqtest/ extend the pattern to HTML forms, XML and testing.

The module path and the repository do not match

The repository is earthboundkid/requests, but go.mod declares:

code
module github.com/carlmjohnson/requests

So the import path and the go get path are carlmjohnson, not earthboundkid. The README also links a note about the canonical project URL, and the badges at the top of the README point at carlmjohnson as well, so the repository was moved without the module path changing.

That is legal in Go. A module path does not have to match where the source is hosted, as long as Go can fetch it from the declared location. It does mean you should copy the module path from go.mod rather than constructing it from the repository URL, which is exactly the mistake most people make on first contact.

The runtime requirement is Go 1.25.0, and there is one require line, golang.org/x/net at v0.58.0. That sits awkwardly against the feature list, which claims no third party dependencies. Both statements come from the project, and the honest reading is that the package's own code pulls in nothing beyond the standard library while x/net is used for an underlying capability, most likely the HTML handling in reqhtml/.

Version history is close to the last push, with v0.26.2 tagged on 2026-10-05, v0.26.1 on 2026-08-20 and v0.25.1 on 2025-09-10. Still on a 0.x series, so minor versions may carry breaking changes under Go's own versioning rules. Licence is MIT, and there is a SECURITY.md at the root.

When a fluent client is the wrong abstraction

The first wrong-tool case is a codebase that wants one specific request shape done exactly once. Wrapping five lines of net/http in a builder adds a dependency and a chain to read, and gives you nothing back. The value here is cumulative across many call sites, not per call.

The second is code that needs non-standard status handling as the default. Because status codes are checked by default, a client that legitimately needs to treat a 404 as a normal outcome has to reach for the validator machinery on every request, which inverts the convenience the package sells. If your API surface returns 404 for expected states, that is a real friction point.

The third is HTTP/2 and connection tuning done manually. Custom transports are supported, but you are assembling a transport in a fluent chain rather than configuring an http.Transport, so intricate transport setup tends to read worse here than it would with the standard library directly.

The alternative is plain net/http with a small helper of your own. You keep full control, no dependency, and no builder to learn, at the cost of writing the body closing and status checking discipline into your own code and hoping it holds across a team. For an application making three or four requests total, that is the right trade. For a service making requests everywhere, the discipline argument stops being about code and starts being about how many bugs you will have next year.

Editorial conclusion

Adopt requests if your Go code makes many outbound HTTP calls and you keep losing lines to body closing, status checks and context plumbing, since the Builder removes all three by construction rather than by convention. Do not adopt it to avoid net/http, because every request still goes through http.Client and you should be comfortable with that client anyway. Before you import it, note that go.mod declares the module as github.com/carlmjohnson/requests, so that is the string your go get needs, even though the code lives in the earthboundkid/requests repository.

Frequently asked questions

How do I import the requests package for Go?

Using the module path from go.mod, which is github.com/carlmjohnson/requests. The repository is earthboundkid/requests, but the module declaration and the badges both use carlmjohnson.

What Go version does requests require?

go.mod declares go 1.25.0. The only require line is golang.org/x/net at v0.58.0, while the README's feature list states there are no third party dependencies.

How do I record and replay HTTP requests in tests with requests?

Install reqtest.Record with a directory as the transport to record a request to the filesystem, then use reqtest.Replay with the same directory in tests. Both are set with Transport, so the request code is unchanged between the two.

Does requests check HTTP status codes automatically?

Yes. Status codes are checked by default, so a non-2xx response becomes an error. Custom validators exist to override that behaviour, with validator.go and validator_handler.go in the repository.

How do I send and receive JSON with the requests package?

Chain BodyJSON with ToJSON and end with Fetch. For example: requests.URL("https://example.com/my-json").BodyJSON(&body).ToJSON(&resp).Fetch(ctx).

Official sources

  1. earthboundkid/requests on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/earthboundkid-requests.svg)](https://hysenlabs.com/projects/earthboundkid-requests)