What it is
Resty is a simple, chainable HTTP and REST client for Go, adding retries, middleware, automatic marshalling and response parsing on top of net/http.
Resty exposes a fluent builder for requests: set headers, query parameters, body and expected result type, then execute. It supports automatic retry with backoff, request and response middleware, and unmarshalling straight into a struct.
Installation
go get github.com/go-resty/resty/v2Getting started
The smallest useful thing you can do with it, and what each part means.
type Book struct {
ID string `json:"id"`
Title string `json:"title"`
}
client := resty.New().
SetBaseURL("https://api.example.com").
SetTimeout(10 * time.Second)
var book Book
resp, err := client.R().
SetContext(ctx).
SetResult(&book). // decoded automatically on 2xx
Get("/books/42")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
if resp.IsError() {
return fmt.Errorf("api returned %d", resp.StatusCode())
}client := resty.New().
SetRetryCount(3).
SetRetryWaitTime(200 * time.Millisecond).
SetRetryMaxWaitTime(2 * time.Second).
AddRetryCondition(func(r *resty.Response, err error) bool {
return err != nil || r.StatusCode() >= 500
})
resp, err := client.R().
SetBody(Book{Title: "Dune"}).
SetHeader("Idempotency-Key", key).
Post("/books")Advanced usage
Where the library earns its place over a simpler alternative.
client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
token, err := tokens.Current(r.Context())
if err != nil {
return err
}
r.SetHeader("Authorization", "Bearer "+token)
return nil
})
client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
slog.Info("upstream call",
"method", r.Request.Method,
"url", r.Request.URL,
"status", r.StatusCode(),
"duration", r.Time())
return nil
})Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- SetResult struct is empty despite a 200 response
- Resty decodes based on the Content-Type header. If the server returns text/plain, use SetHeader("Accept", "application/json") or decode resp.Body() manually.
- context deadline exceeded
- The client timeout or request context expired. Distinguish the two: SetTimeout covers the whole request, while the context may be cancelled by an upstream caller.
Best practices
- Create one client and reuse it — a new client per request defeats connection pooling.
- Always check resp.IsError(); err only reports transport-level failures, not HTTP error statuses.
- Pass a context with SetContext so requests are cancelled when the caller goes away.
- Restrict retries to idempotent requests, or attach an idempotency key.
Background
Why it exists, and what it was reacting to.
Resty was created by Jeevanandam M. to remove the repetitive boilerplate of building requests, checking status codes and decoding bodies with net/http. It keeps the standard client underneath, so timeouts and transports remain configurable.
