Kickstarts your API by providing out-of-the-box implementations for must-have modules and components for a successful API.
Provides a solid foundation for SaaS, Client/Server and API products. It provides out-of-the-box mitigations for the 10 OWASP risks for APIs:
go get https://github.com/stfsy/go-api-kit
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/stfsy/go-api-kit/server"
)
var s *server.Server
func main() {
startServerNonBlocking()
stopServerAfterSignal()
}
func stopServerAfterSignal() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
s.Stop()
fmt.Println("Graceful shutdown complete.")
}
func startServerNonBlocking() {
s = server.NewServer(&server.ServerConfig{
MuxCallback: func(*http.ServeMux) {
// add your endpoints and middlewares here
},
ListenCallback: func() {
// do sth just after listen was called on the server instance and
// just before the server starts serving requests
},
// port override is optional but can be used if you want to
// define the port manually. If empty the value of env.PORT is used.
PortOverride: "8080",
})
go func() {
err := s.Start()
if err != nil {
panic(fmt.Errorf("unable to start server %w", err))
}
}()
}
This module will read the following environment variables.
API_KIT_ENV
: default=productionAPI_KIT_MAX_BODY_SIZE
: default=10485760 (bytes) = 10 MBAPI_KIT_READ_TIMEOUT
: default=10 (seconds)API_KIT_WRITE_TIMEOUT
: default=10 (seconds)API_KIT_IDLE_TIMEOUT
: default=620 (seconds)
PORT
: default=8080
The module provides several ready-to use middlewares which are compatible with e.g. https://github.com/urfave/negroni.
Logs each incoming request to give insights about usage and response times.
import (
"net/http"
"github.com/urfave/negroni"
"github.com/stfsy/go-api-kit/server/middlewares"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
n := negroni.New()
n.Use(middlewares.NewAccessLog())
n.UseHandler(mux)
http.ListenAndServe(":8080", n)
}
Validates the incoming content type, if the request method implies a state change (e.g. POST).
import (
"net/http"
"github.com/urfave/negroni"
"github.com/stfsy/go-api-kit/server/middlewares"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
n := negroni.New()
n.Use(middlewares.NewRequireContentTypeMiddleware("application/json"))
n.UseHandler(mux)
http.ListenAndServe(":8080", n)
}
Limits the maximum allowed size of the request body.
import (
"net/http"
"github.com/urfave/negroni"
"github.com/stfsy/go-api-kit/server/middlewares"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
n := negroni.New()
n.Use(middlewares.NewRequireMaxBodyLengthMiddleware())
n.UseHandler(mux)
http.ListenAndServe(":8080", n)
}
Adds additional security headers to the response to prevent common attacks and protect users and their data.
import (
"net/http"
"github.com/urfave/negroni"
"github.com/stfsy/go-api-kit/server/middlewares"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
n := negroni.New()
n.Use(middlewares.NewRespondWithSecurityHeadersMiddleware())
n.UseHandler(mux)
http.ListenAndServe(":8080", n)
}
Instructs proxy servers between the client and the API to not cache responses.
import (
"net/http"
"github.com/urfave/negroni"
"github.com/stfsy/go-api-kit/server/middlewares"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
n := negroni.New()
n.Use(middlewares.NewNoCacheHeadersMiddleware())
n.UseHandler(mux)
http.ListenAndServe(":8080", n)
}
These functions help you send plain text or JSON responses easily:
Sends a plain text response.
import "github.com/stfsy/go-api-kit/server/handlers"
handlers.SendText(w, "Hello, world!")
Sends a JSON response (sets Content-Type to application/json).
import "github.com/stfsy/go-api-kit/server/handlers"
handlers.SendJson(w, []byte(`{"message":"ok"}`))
Sends a struct as a JSON response (sets Content-Type to application/json). The struct is marshaled to JSON automatically.
import "github.com/stfsy/go-api-kit/server/handlers"
type MyResponse struct {
Status int `json:"status"`
Title string `json:"title"`
}
handlers.SendStructAsJson(w, MyResponse{Status: 200, Title: "Success"})
These functions send standardized error responses with the correct HTTP status code and a JSON body. Each function takes an http.ResponseWriter
and an optional details
map for additional error info. The error response now uses the following structure:
{
"status": 400,
"title": "Bad Request",
"details": {
"zip_code": {
"validator": "required",
"message": "must not be undefined",
}
}
}
import "github.com/stfsy/go-api-kit/server/handlers"
// With details (field-level errors):
handlers.SendBadRequest(w, handlers.ErrorDetails{
"zip_code": {
"validator": "required",
"message": "must not be undefined",
}
})
handlers.SendUnauthorized(w, handlers.ErrorDetails{
"x-api-key": {
"message": "must not be null",
},
})
// Without details (generic error):
handlers.SendInternalServerError(w, nil)
Status Code | Title | Function |
---|---|---|
400 | Bad Request | SendBadRequest |
401 | Unauthorized | SendUnauthorized |
403 | Forbidden | SendForbidden |
404 | Not Found | SendNotFound |
405 | Method Not Allowed | SendMethodNotAllowed |
406 | Not Acceptable | SendNotAcceptable |
408 | Request Timeout | SendRequestTimeout |
409 | Conflict | SendConflict |
410 | Gone | SendGone |
411 | Length Required | SendLengthRequired |
412 | Precondition Failed | SendPreconditionFailed |
413 | Payload Too Large | SendPayloadTooLarge |
414 | URI Too Long | SendURITooLong |
415 | Unsupported Media Type | SendUnsupportedMediaType |
416 | Range Not Satisfiable | SendRangeNotSatisfiable |
417 | Expectation Failed | SendExpectationFailed |
422 | Unprocessable Entity | SendUnprocessableEntity |
429 | Too Many Requests | SendTooManyRequests |
500 | Internal Server Error | SendInternalServerError |
501 | Not Implemented | SendNotImplemented |
502 | Bad Gateway | SendBadGateway |
503 | Service Unavailable | SendServiceUnavailable |
504 | Gateway Timeout | SendGatewayTimeout |
505 | HTTP Version Not Supported | SendHTTPVersionNotSupported |
Wraps your handler to automatically decode and validate JSON request bodies for POST, PUT, and PATCH methods. For other methods, the handler receives nil as the payload.
To enable JSON payload validation, add https://github.com/go-playground/validator compatible tags to your struct.
import "github.com/stfsy/go-api-kit/server/handlers"
type MyPayload struct {
Name string `json:"name" validate:"required"`
}
handlers.ValidatingHandler[MyPayload](func(w http.ResponseWriter, r *http.Request, p *MyPayload) {
// Use validated payload
w.Write([]byte(p.Name))
})
Validation errors will be sent to the client automatically with status code 400
. The response will have content type application/problem+json
. Here's an example:
{
"status": 400,
"title": "Bad Request",
"details": {
"zip_code": {
"validator": "required",
"message": "must not be undefined",
}
}
}
To run tests, run the following command
./test.sh