Servekit is a small Go package for bootstrapping HTTP services on top of net/http. It gives a service a real operational baseline from the first constructor call: probes, JSON response handling, request and correlation IDs, access logs, panic recovery, graceful shutdown, opt-in CORS, and built-in OpenTelemetry tracing and metrics.
It is especially useful for APIs and microservices that want consistent HTTP bootstrap without adopting a full web framework.
Servekit is not a framework. You still work with http.Request, http.Handler, and http.ServeMux. The point is to stop rebuilding the same service bootstrap around them.
It also does not lock you into only the built-in stack. Services can add their own global middleware with WithMiddleware(...) and route-local middleware with WithEndpointMiddleware(...) while staying on the normal Servekit path.
Many Go services spend a meaningful amount of code on the same HTTP setup work: configuring http.Server, wiring middleware, handling panics, exposing probes, publishing build information, and shutting down cleanly.
That work matters, but it usually gets rewritten service by service and drifts a little each time. Servekit pulls it into a small, net/http-first package so new services can start from one coherent baseline and spend more code on domain behavior.
Its goal is narrow: own the reusable HTTP bootstrap layer, not the whole application.
Servekit is not a web framework. It does not replace net/http, add its own router DSL, or impose a different application model.
It also does not try to own dependency injection, background work, config loading, or service discovery. Those concerns stay with the application.
It is not a full observability platform either. Servekit gives the HTTP layer a strong default baseline, but the application still owns its telemetry backend, dashboards, alerting, and broader operational policy.
Servekit is a good fit when:
- you want to stay on
net/httpbut stop rebuilding the same service bootstrap around it - you want built-in probes, readiness, request IDs, access logging, panic recovery, OpenTelemetry, and graceful shutdown from the start
- you want strong defaults without giving up application-owned middleware
- you want one package that can cover both ordinary request/response routes and raw
http.Handlerendpoints such as streaming, proxying, and upgrades
Servekit is probably not a fit when:
- you want a batteries-included web framework or a non-stdlib routing model
- your service already has a settled bootstrap stack and Servekit would just duplicate it
- you mainly want a full framework abstraction rather than a
net/http-first bootstrap layer
go get github.com/jaredjakacky/servekitimport servekit "github.com/jaredjakacky/servekit"package main
import (
"context"
"log"
"net/http"
servekit "github.com/jaredjakacky/servekit"
)
func main() {
s := servekit.New()
s.Handle(http.MethodGet, "/coffee", func(r *http.Request) (any, error) {
return map[string]string{
"drink": "coffee",
"status": "ready",
}, nil
})
if err := s.Run(context.Background()); err != nil {
log.Fatal(err)
}
}Run() starts the server on the configured address, which defaults to :8080, marks it ready once it begins serving, and handles shutdown on cancellation or SIGINT / SIGTERM.
That one server already gives you:
- JSON success and error encoding for
Handle - built-in
GET /livez,GET /readyz, andGET /version - request IDs and correlation IDs
- access logging and panic recovery
- OpenTelemetry request tracing and request metrics
Run(...)-path connection metrics- readiness transitions and graceful shutdown on
SIGINTandSIGTERM - conservative timeout and request body defaults
- built-in use of global OpenTelemetry providers and propagators unless you override them
When the service needs application-specific policy on top of that baseline, add global middleware with WithMiddleware(...) or route-local behavior with WithEndpointMiddleware(...) rather than replacing the whole setup.
By default, panic recovery logs the panic and stack trace, returns a best-effort JSON 500 when the response is still uncommitted, and leaves committed responses alone.
In practice, you get a real HTTP baseline without hand-building the http.Server lifecycle, middleware stack, probes, IDs, telemetry, and default request/response behavior yourself.
Servekit is deliberately built around one normal path and one escape hatch.
Use Handle for the endpoints that naturally want to:
- inspect the request
- do application work
- return one payload or one error
s.Handle(http.MethodGet, "/users/me", func(r *http.Request) (any, error) {
return map[string]string{
"id": "123",
"name": "jared",
}, nil
})Use HandleHTTP when the endpoint needs direct net/http control, such as
streaming, proxying, upgrades, or mounting an existing http.Handler:
s.HandleHTTP(http.MethodGet, "/events", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.WriteHeader(http.StatusOK)
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "streaming unsupported", http.StatusInternalServerError)
return
}
for i := 0; i < 3; i++ {
_, _ = fmt.Fprintf(w, "data: tick %d\n\n", i)
flusher.Flush()
time.Sleep(500 * time.Millisecond)
}
}))Typical reasons are streaming, server-sent events, reverse proxying, protocol upgrades, or handlers that already exist as http.Handler. Servekit preserves runtime writer capabilities such as http.Flusher, http.Hijacker, and the io.ReaderFrom fast path when the underlying writer supports them, so the raw path stays credible.
Servekit also uses the underlying http.ServeMux routing model rather than inventing its own router DSL. That is intentional: the package is meant to standardize the service baseline around the standard library, not to replace the standard library routing story.
Servekit rests on three choices:
- It keeps the standard library visible.
- It ships a coherent operational baseline instead of scattered one-off setup.
- It lets the service become more specialized without forcing a rewrite onto a different abstraction model.
That is why the package can stay small without feeling toy-sized.
Servekit has a short normal path, but it is not boxed into only the defaults. Advanced hooks include:
- global and route-level custom middleware
- custom success and error encoders, globally or per endpoint
- integration with an existing
http.ServeMux - mounting
Handler()into your ownhttp.Server - explicit readiness control with
SetReady(...) - custom
slogandhttp.Server.ErrorLogwiring - CORS configuration
- OpenTelemetry provider, propagator, and labeling customization
- raw-response handling for streaming, proxying, and hijacking
The advanced path is documented in docs/advanced.md, including composition patterns for combining several hooks without losing the main Servekit model.
- Getting Started: first service, first run, first curl
- Usage Guide: the normal path, default behavior, and recommended adoption flow
- Advanced Guide: custom encoders, composition patterns, external server ownership, telemetry customization, and other advanced hooks
- Lifecycle and Probes: readiness,
/livez,/readyz,/healthz, and shutdown - Observability and Middleware: IDs, access logs, panic recovery, OpenTelemetry, and CORS
- API Map: human-friendly map of the exported surface
- Examples Guide: how the runnable examples build from the core path outward
- Examples Directory: quick index of the runnable example programs
Runnable programs live in examples/, which includes a guided tour of the example set.
Recommended reading order:
examples/basicexamples/telemetryexamples/endpoint-controlsexamples/custom-encodingexamples/readinessexamples/loggingexamples/corsexamples/external-serverexamples/advanced-compositionexamples/streamingexamples/reverse-proxyexamples/response-capture
The canonical symbol-level API documentation should live in Go doc comments so it stays accurate in editors and Go tooling. The repository-level companion is docs/api.md, which groups the exported surface into a human-oriented map.
Servekit is a small open source library maintained on a best-effort basis.
The active development line lives on main, and that is the only line actively maintained unless explicitly noted otherwise. The minimum supported Go version is declared in go.mod, and the Go versions currently verified in CI are listed in .github/workflows/ci.yaml.
Compatibility-impacting changes should be called out explicitly in release notes or release descriptions. Long-lived maintenance branches and backports are not planned unless explicitly noted.
This repository uses make for local verification:
make verify
make build-examples
make test-race
make govulncheckmake verify checks formatting, runs go vet, runs tests, builds the runnable examples, and verifies that go.mod and go.sum are tidy. make build-examples is available when you only want to compile the runnable examples.
CI runs verification and race tests on the supported Go versions. Release tags are gated by those jobs plus govulncheck before publishing.
Bug reports, documentation fixes, small API ergonomics improvements, and compatibility issues are welcome.
Servekit is intentionally scoped as a small net/http-first service bootstrap library. Large framework features are likely out of scope, including custom router DSLs, dependency injection, config loading, background job systems, service discovery, and observability backend management.
For security issues, please follow SECURITY.md instead of opening a public issue.