apirouter

package module
v0.11.2 Latest Latest
Warning

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

Go to latest
Published: May 28, 2025 License: MIT Imports: 21 Imported by: 11

README

go-api-router

Lightweight API httprouter middleware: cors, logging, and standardized error handling.

Release Build Status Report codecov Go
Sponsor Donate


Table of Contents


Installation

go-api-router requires a supported release of Go.

go get -u github.com/mrz1836/go-api-router

Documentation

View the generated documentation

GoDoc

Features
  • Uses the fastest router: Julien Schmidt's httprouter
  • Uses gofr's uuid package to guarantee unique request ids
  • Uses MrZ's go-logger for either local or remote logging via Log Entries (Rapid7)
  • Uses MrZ's go-parameters for parsing any type of incoming parameter with ease
  • Optional: NewRelic support!
  • Added basic middleware support from Rileyr's middleware
  • Optional: JWT Authentication (middleware)
  • Added additional CORS functionality
  • Standardized error responses for API requests
  • Centralized logging on all requests (requesting user info and request time)
  • Custom response writer for Etag and cache support
  • GetClientIPAddress() safely detects IP addresses behind load balancers
  • GetParams() parses parameters only once
  • FilterMap() removes any confidential parameters from logs
  • ...and more!
Package Dependencies
Library Deployment

goreleaser for easy binary or library deployment to GitHub and can be installed via: brew install goreleaser.

The .goreleaser.yml file is used to configure goreleaser.

Use make release-snap to create a snapshot version of the release, and finally make release to ship to production.

Makefile Commands

View all makefile commands

make help

List of all current commands:

all                  Runs lint, test and vet
clean                Remove previous builds and any test cache data
clean-mods           Remove all the Go mod cache
coverage             Shows the test coverage
godocs               Sync the latest tag with GoDocs
help                 Show this help message
install              Install the application
install-go           Install the application (Using Native Go)
lint                 Run the golangci-lint application (install if not found)
release              Full production release (creates release in Github)
release              Runs common.release then runs godocs
release-snap         Test the full release (build binaries)
release-test         Full production test release (everything except deploy)
replace-version      Replaces the version in HTML/JS (pre-deploy)
run-examples         Runs all the examples
tag                  Generate a new tag and push (tag version=0.0.0)
tag-remove           Remove a tag if found (tag-remove version=0.0.0)
tag-update           Update an existing tag to current commit (tag-update version=0.0.0)
test                 Runs vet, lint and ALL tests
test-ci              Runs all tests via CI (exports coverage)
test-ci-no-race      Runs all tests via CI (no race) (exports coverage)
test-ci-short        Runs unit tests via CI (exports coverage)
test-short           Runs vet, lint and tests (excludes integration tests)
uninstall            Uninstall the application (and remove files)
update-linter        Update the golangci-lint package (macOS only)
vet                  Run the Go vet application

Examples & Tests

All unit tests and examples run via GitHub Actions and use Go version 1.23.x. View the configuration file.

Run all tests (including integration tests)

make test

Run tests (excluding integration tests)

make test-short

Run the examples:

make run-examples

Benchmarks

Run the Go benchmarks:

make bench

Code Standards

Read more about this Go project's code standards.


Usage

View the examples

Basic implementation:

package main

import (
	"fmt"
	"net/http"

	"github.com/julienschmidt/httprouter"
	"github.com/mrz1836/go-api-router"
	"github.com/mrz1836/go-logger"
)

func main() {
	// Load the router & middleware
	router := apirouter.New()

	// Set the main index page (navigating to slash)
	router.HTTPRouter.GET("/", router.Request(index))

	// Serve the router!
	logger.Fatalln(http.ListenAndServe(":3000", router.HTTPRouter))
}

func index(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
	_, _ = fmt.Fprint(w, "This is a simple route example!")
}

Maintainers

MrZ
MrZ

Contributing

View the contributing guidelines and please follow the code of conduct.

How can I help?

All kinds of contributions are welcome 🙌! The most basic way to show your support is to star 🌟 the project, or to raise issues 💬. You can also support this project by becoming a sponsor on GitHub 👏 or by making a bitcoin donation to ensure this journey continues indefinitely! 🚀

Stars


License

License

Documentation

Overview

Package apirouter provides lightweight HTTP middleware for CORS handling, structured logging, and standardized API response formatting.

It is designed to integrate seamlessly with Julien Schmidt's httprouter and leverages the go-logger package by MrZ for consistent logging across services.

Index

Examples

Constants

View Source
const (
	LogErrorFormat  string = "request_id=\"%s\" ip_address=\"%s\" type=\"%s\" internal_message=\"%s\" code=%d\n"
	LogPanicFormat  string = "request_id=\"%s\" method=\"%s\" path=\"%s\" type=\"%s\" error_message=\"%s\" stack_trace=\"%s\"\n"
	LogParamsFormat string = "request_id=\"%s\" method=\"%s\" path=\"%s\" ip_address=\"%s\" user_agent=\"%s\" params=\"%v\"\n"
	LogTimeFormat   string = "request_id=\"%s\" method=\"%s\" path=\"%s\" ip_address=\"%s\" user_agent=\"%s\" service=%dms status=%d\n"
)

Log formats for the request

View Source
const (

	// AuthorizationHeader is the auth header
	AuthorizationHeader = "Authorization"

	// AuthorizationBearer is the second part of the auth header
	AuthorizationBearer = "Bearer"

	// CookieName is for the secure cookie that also has the JWT token
	CookieName = "jwt_token"
)
View Source
const (
	// ErrCodeUnknown unknown error code (example)
	ErrCodeUnknown int = 600

	// StatusCodeUnknown unknown HTTP status code (example)
	StatusCodeUnknown int = 600
)

Variables

View Source
var ErrClaimsValidationFailed = errors.New("claims failed validation")

ErrClaimsValidationFailed is when the claim's validation has failed

View Source
var ErrHeaderInvalid = errors.New("authorization header was mal-formatted or missing")

ErrHeaderInvalid is when the header is missing or invalid

View Source
var ErrInvalidSessionID = errors.New("invalid session id detected")

ErrInvalidSessionID is when the session id is invalid or missing

View Source
var ErrInvalidSigningMethod = errors.New("invalid signing method")

ErrInvalidSigningMethod is when the signing method is invalid

View Source
var ErrInvalidUserID = errors.New("invalid user id detected")

ErrInvalidUserID is when the user ID is invalid or missing

View Source
var ErrIssuerMismatch = errors.New("issuer did not match")

ErrIssuerMismatch is when the issuer does not match the system issuer

View Source
var ErrJWTInvalid = errors.New("jwt was invalid")

ErrJWTInvalid is when the JWT payload is invalid

Functions

func Check added in v0.5.0

func Check(w http.ResponseWriter, r *http.Request, sessionSecret, issuer string,
	sessionAge time.Duration) (authenticated bool, req *http.Request, err error)

Check will check if the JWT is present and valid in the request and then extend the token

func ClearToken added in v0.5.0

func ClearToken(w http.ResponseWriter, req *http.Request)

ClearToken will remove the token from the response and request

func CreateToken added in v0.5.0

func CreateToken(sessionSecret, userID, issuer, sessionID string,
	expiration time.Duration) (string, error)

CreateToken will make the claims, and then make/sign the token

func FilterMap added in v0.1.12

func FilterMap(params *parameters.Params, filterOutFields []string) (filtered *parameters.Params)

FilterMap will filter the parameters and not log parameters with sensitive data. To add more parameters - see the if in the loop.

func FindString added in v0.0.10

func FindString(needle string, haystack []string) int

FindString returns the index of the first instance of needle in the array or -1 if it could not be found

func GetAuthToken added in v0.0.23

func GetAuthToken(req *http.Request) (token string, ok bool)

GetAuthToken gets the stored authentication token from the request

func GetClientIPAddress

func GetClientIPAddress(req *http.Request) string

GetClientIPAddress gets the client ip address

func GetCustomData added in v0.0.24

func GetCustomData(req *http.Request) (data interface{})

GetCustomData gets the stored custom data

func GetIPFromRequest added in v0.0.15

func GetIPFromRequest(req *http.Request) (ip string, ok bool)

GetIPFromRequest gets the stored ip from the request if found

func GetParams

func GetParams(req *http.Request) *parameters.Params

GetParams gets the params from the http request (parsed once on log request) Helper method for the parameters package

func GetRequestID added in v0.0.15

func GetRequestID(req *http.Request) (id string, ok bool)

GetRequestID gets the stored request id from the request

func GetTokenFromHeader added in v0.5.0

func GetTokenFromHeader(w http.ResponseWriter) string

GetTokenFromHeader will get the token value from the header

func GetTokenFromHeaderFromRequest added in v0.5.0

func GetTokenFromHeaderFromRequest(req *http.Request) string

GetTokenFromHeaderFromRequest will get the token value from the request

func GetTokenFromResponse added in v0.5.0

func GetTokenFromResponse(res *http.Response) string

GetTokenFromResponse will get the token value from the HTTP response

func JSONEncode added in v0.0.10

func JSONEncode(e *json.Encoder, objects interface{}, allowed []string) error

JSONEncode will encode only the allowed fields of the models

func JSONEncodeHierarchy added in v0.0.10

func JSONEncodeHierarchy(w io.Writer, objects interface{}, allowed interface{}) error

JSONEncodeHierarchy will execute JSONEncode for multiple nested objects

func NoCache added in v0.0.22

func NoCache(w http.ResponseWriter, req *http.Request)

NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent a router (or subrouter) from being cached by an upstream proxy and/or client.

As per http://wiki.nginx.org/HttpProxyModule - NoCache sets:

Expires: Thu, 01 Jan 1970 00:00:00 UTC
Cache-Control: no-cache, private, max-age=0
X-Accel-Expires: 0
Pragma: no-cache (for HTTP/1.0 proxies/clients)

func PermitParams added in v0.0.17

func PermitParams(params *parameters.Params, allowedKeys []string)

PermitParams will remove all keys that not allowed Helper method for the parameters package

func RespondWith added in v0.11.0

func RespondWith(w http.ResponseWriter, _ *http.Request, status int, data interface{})

RespondWith writes a JSON response with the specified status code and data to the ResponseWriter. It sets the "Content-Type" header to "application/json; charset=utf-8". The data is serialized to JSON.

If data is an error, it responds with a JSON object {"error": <error message>}. If data is nil and the status is an error (>= 400), it responds with {"error": <StatusText>, "code": <status>}. If the status is 204 (No Content) or 304 (Not Modified), no response body is sent.

This function ensures a single response per request and is safe for use in HTTP handlers.

func ReturnJSONEncode added in v0.0.12

func ReturnJSONEncode(w http.ResponseWriter, code int, e *json.Encoder, objects interface{}, allowed []string) (err error)

ReturnJSONEncode is a mixture of ReturnResponse and JSONEncode

func ReturnResponse added in v0.0.3

func ReturnResponse(w http.ResponseWriter, req *http.Request, code int, data interface{})

ReturnResponse helps return a status code and message to the end user deprecated: use RespondWith instead

func SetAuthToken added in v0.0.23

func SetAuthToken(req *http.Request, authToken string) *http.Request

SetAuthToken set the authentication token on the request

func SetCustomData added in v0.0.24

func SetCustomData(req *http.Request, data interface{}) *http.Request

SetCustomData set the custom data / user profile / permissions / etc

func SetOnRequest added in v0.0.23

func SetOnRequest(req *http.Request, keyName paramRequestKey, value interface{}) *http.Request

SetOnRequest will set the value on the request with the given key

func SetTokenHeader added in v0.5.0

func SetTokenHeader(w http.ResponseWriter, r *http.Request, token string, expiration time.Duration)

SetTokenHeader will set the authentication token on the response and set a cookie

func SnakeCase added in v0.0.10

func SnakeCase(str string) string

SnakeCase takes a camelCaseWord and breaks it into a camel_case_word

func StandardHandlerToHandle added in v0.0.7

func StandardHandlerToHandle(next http.Handler) httprouter.Handle

StandardHandlerToHandle converts a standard middleware to Julien handle version

Types

type APIError

type APIError struct {
	Code            int         `json:"code" url:"code"`                 // Associated error code
	Data            interface{} `json:"data" url:"data"`                 // Arbitrary data that is relevant
	InternalMessage string      `json:"-" url:"-"`                       // An internal message for engineers
	IPAddress       string      `json:"ip_address" url:"ip_address"`     // Current IP of user
	Method          string      `json:"method" url:"method"`             // Method requested (IE: POST)
	PublicMessage   string      `json:"message" url:"message"`           // Public error message
	RequestGUID     string      `json:"request_guid" url:"request_guid"` // Unique Request ID for tracking
	StatusCode      int         `json:"status_code" url:"status_code"`   // Associated HTTP status code (should be in request as well)
	URL             string      `json:"url" url:"url"`                   // Requesting URL
}

APIError is the enriched error message for API related errors

func ErrorFromRequest added in v0.0.16

func ErrorFromRequest(req *http.Request, internalMessage, publicMessage string, errorCode, statusCode int, data interface{}) *APIError

ErrorFromRequest gives an error without a response writer using the request

func ErrorFromResponse added in v0.0.16

func ErrorFromResponse(w *APIResponseWriter, internalMessage, publicMessage string, errorCode, statusCode int, data interface{}) *APIError

ErrorFromResponse generates a new error struct using CustomResponseWriter from LogRequest()

Example

ExampleErrorFromResponse example using ErrorFromResponse()

w := setupTest()
err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`)
fmt.Println(err.Error())
Output:

public message

func (*APIError) Error

func (e *APIError) Error() string

Error returns the string error message (only public message)

Example

ExampleAPIError_Error example using Error()

w := setupTest()
err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`)
fmt.Println(err.Error())
Output:

public message

func (*APIError) ErrorCode

func (e *APIError) ErrorCode() int

ErrorCode returns the error code

Example

ExampleAPIError_ErrorCode example using ErrorCode()

w := setupTest()
err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`)
fmt.Println(err.ErrorCode())
Output:

600

func (*APIError) Internal

func (e *APIError) Internal() string

Internal returns the string error message (only internal message)

Example

ExampleAPIError_Internal example using Internal()

w := setupTest()
err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`)
fmt.Println(err.Internal())
Output:

internal message

func (*APIError) JSON

func (e *APIError) JSON() (string, error)

JSON returns the entire public version of the error message

Example

ExampleAPIError_JSON example using JSON()

w := setupTest()
err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`)
str, _ := err.JSON()
fmt.Println(str)
Output:

{"code":600,"data":"{\"something\":\"else\"}","ip_address":"127.0.0.1","method":"GET","message":"public message","request_guid":"unique-guid-per-user","status_code":600,"url":"/this/path"}

type APIResponseWriter

type APIResponseWriter struct {
	http.ResponseWriter
	Buffer          bytes.Buffer  `json:"-" url:"-"`
	CacheIdentifier []string      `json:"cache_identifier" url:"cache_identifier"`
	CacheTTL        time.Duration `json:"cache_ttl" url:"cache_ttl"`
	IPAddress       string        `json:"ip_address" url:"ip_address"`
	Method          string        `json:"method" url:"method"`
	NoWrite         bool          `json:"no_write" url:"no_write"`
	RequestID       string        `json:"request_id" url:"request_id"`
	Status          int           `json:"status" url:"status"`
	URL             string        `json:"url" url:"url"`
	UserAgent       string        `json:"user_agent" url:"user_agent"`
}

APIResponseWriter wraps the ResponseWriter and stores the status of the request. It is used by the LogRequest middleware

func (*APIResponseWriter) AddCacheIdentifier

func (r *APIResponseWriter) AddCacheIdentifier(identifier string)

AddCacheIdentifier add cache identifier to the response writer

func (*APIResponseWriter) Header

func (r *APIResponseWriter) Header() http.Header

Header returns the http.Header that will be written to the response

func (*APIResponseWriter) StatusCode added in v0.0.5

func (r *APIResponseWriter) StatusCode() int

StatusCode give a way to get the status code

func (*APIResponseWriter) Write

func (r *APIResponseWriter) Write(data []byte) (int, error)

Write writes the data out to the client, if WriteHeader was not called, it will write status http.StatusOK (200)

func (*APIResponseWriter) WriteHeader

func (r *APIResponseWriter) WriteHeader(status int)

WriteHeader will write the header to the client, setting the status code

type AllowedKeys added in v0.0.10

type AllowedKeys map[string]interface{}

AllowedKeys is for allowed keys

type Claims added in v0.5.0

type Claims struct {
	jwt.RegisteredClaims        // Updated to use RegisteredClaims
	UserID               string `json:"user_id"` // The user ID set on the claims
}

Claims is our custom JWT claims

func GetClaims added in v0.5.0

func GetClaims(req *http.Request) Claims

GetClaims will return the current claims from the request

func (Claims) CreateToken added in v0.5.0

func (c Claims) CreateToken(expiration time.Duration, sessionSecret string) (string, error)

CreateToken will make a token from claims

func (Claims) IsEmpty added in v0.5.0

func (c Claims) IsEmpty() bool

IsEmpty will detect if the claims are empty or not

func (Claims) Verify added in v0.5.0

func (c Claims) Verify(issuer string) (bool, error)

Verify will check the claims against known verifications

type InternalStack added in v0.0.18

type InternalStack struct {
	// contains filtered or unexported fields
}

InternalStack internal stack type

func NewStack added in v0.0.6

func NewStack() *InternalStack

NewStack will create an InternalStack struct

func (*InternalStack) Use added in v0.0.18

func (s *InternalStack) Use(mw Middleware)

Use adds the middleware to the list

func (*InternalStack) Wrap added in v0.0.18

Wrap wraps the router

type LoggerInterface added in v0.7.0

type LoggerInterface interface {
	Printf(format string, v ...interface{})
}

LoggerInterface is the logger interface

type Middleware added in v0.0.6

type Middleware func(httprouter.Handle) httprouter.Handle

Middleware is the Handle implementation

func StandardHandlerToMiddleware added in v0.0.8

func StandardHandlerToMiddleware(next http.Handler) Middleware

StandardHandlerToMiddleware converts standard middleware to type Middleware

type Router added in v0.0.2

type Router struct {
	AccessControlExposeHeaders  string               `json:"access_control_expose_headers" url:"access_control_expose_headers"`   // Allow specific headers for cors
	CrossOriginAllowCredentials bool                 `json:"cross_origin_allow_credentials" url:"cross_origin_allow_credentials"` // Allow credentials for BasicAuth()
	CrossOriginAllowHeaders     string               `json:"cross_origin_allow_headers" url:"cross_origin_allow_headers"`         // Allowed headers
	CrossOriginAllowMethods     string               `json:"cross_origin_allow_methods" url:"cross_origin_allow_methods"`         // Allowed methods
	CrossOriginAllowOrigin      string               `json:"cross_origin_allow_origin" url:"cross_origin_allow_origin"`           // Custom value for allow origin
	CrossOriginAllowOriginAll   bool                 `json:"cross_origin_allow_origin_all" url:"cross_origin_allow_origin_all"`   // Allow all origins
	CrossOriginEnabled          bool                 `json:"cross_origin_enabled" url:"cross_origin_enabled"`                     // Enable or Disable CrossOrigin
	FilterFields                []string             `json:"filter_fields" url:"filter_fields"`                                   // Filter out protected fields from logging
	HTTPRouter                  *nrhttprouter.Router `json:"-" url:"-"`                                                           // NewRelic wrapper for J Schmidt's httprouter
	Logger                      LoggerInterface      `json:"-" url:"-"`                                                           // Logger interface
	SkipLoggingPaths            []string             `json:"skip_logging_paths" url:"skip_logging_paths"`                         // Skip logging on these paths (IE: /health)
	// contains filtered or unexported fields
}

Router is the configuration for the middleware service

func New

func New() *Router

New returns a router middleware configuration to use for all future requests

func NewWithNewRelic added in v0.4.6

func NewWithNewRelic(app *newrelic.Application) *Router

NewWithNewRelic returns a router middleware configuration with NewRelic enabled

func (*Router) BasicAuth added in v0.0.2

func (r *Router) BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string, errorResponse interface{}) httprouter.Handle

BasicAuth wraps a request for Basic Authentication (RFC 2617)

func (*Router) Request added in v0.0.2

func (r *Router) Request(h httprouter.Handle) httprouter.Handle

Request will write the request to the logs before and after calling the handler

func (*Router) RequestNoLogging added in v0.0.2

func (r *Router) RequestNoLogging(h httprouter.Handle) httprouter.Handle

RequestNoLogging will just call the handler without any logging Used for API calls that do not require any logging overhead

func (*Router) SetCrossOriginHeaders added in v0.0.4

func (r *Router) SetCrossOriginHeaders(w http.ResponseWriter, req *http.Request, _ httprouter.Params)

SetCrossOriginHeaders sets the cross-origin headers if enabled todo: combine this method and the GlobalOPTIONS http.HandlerFunc() method (@mrz had an issue combining)

type Stack added in v0.0.6

type Stack interface {
	/*
		Adds middleware to the InternalStack. MWs will be
		called in the same order that they are added
		such that:
			Use(Request ID Middleware)
			Use(Request Timing Middleware)
		would result in the request id middleware being
		the outermost layer, called first, before the
		timing middleware.
	*/
	// Use for adding new middlewares
	Use(m Middleware)

	/*
		Wraps a given handle with the current InternalStack
		from the result of Use() calls.
	*/
	// Wrap wraps the router
	Wrap(h httprouter.Handle) httprouter.Handle
}

Stack is the interface for middleware

Directories

Path Synopsis
Package main shows examples using the API Router
Package main shows examples using the API Router

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL