Verto is a simple REST framework that provides routing, error handling, response handling,
logging, and middleware chaining.
- Basic Usage
- Resource Function
- Routing
- Path Redirection
- Middleware Chaining
- Groups
- Context
- Response Handler
- Error Handler
- Injections
- Logging
// Initialize Verto
v := verto.New()
// Register a route
v.Add("GET", "/", func(c *verto.Context) (interface{}, error) {
fmt.Fprintf(c.Response, "Hello, World!")
})
// Run Verto
v.Run()
Verto accepts a custom ResourceFunction:
type ResourceFunc(c *Context) (interface{}, error) func
The function takes in a Verto Context struct. The Context contains
a map
of injections that we will discuss later. The resource function
returns an interface{}
and optionally an error
. If the call to the resource was successful,
a nil error
should be returned. The default Verto instance will be able to handle any error
s
or interface{}
return values but in a very basic way. It is recommended for the user to bring
a custom response and error handler to Verto.
Verto will also happily take http.Handler
as a routing endpoint.
Verto offers simple routing. Endpoints are registered using the Add
and AddHandker
methods. The parameters are the method to be matched, the path to be matched, and the endpoint
handler. Add
takes a verto.ResourceFunc
as an endpoint and AddHandler
takes a
normal http.Handler
.
// Basic routing example
endpoint1 := verto.ResourceFunc(c *verto.Context) (interface{}, error) {
fmt.Fprintf(c.Response, "Hello, World!")
})
endpoint2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
v.Add("GET", "/path/to/1", endpoint1)
v.Add("POST", "/path/to/1", endpoint1)
v.AddHandler("PUT", "/path/to/2", endpoint2)
Verto also includes the option for named parameters in the path. Named parameters can
be more strictly defined using regular expressions. Named parameters will be injected into
r.FormValue()
and, if the endpoint is a ResourceFunc
, will be retrievable through the
Context utility functions.
// Named routing example
endpoint1 := verto.ResourceFunc(c *verto.Context) (interface{}, error) {
fmt.Fprintf(c.Response, c.Get("param"))
})
endpoint2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, r.FormValue("param"))
})
// Named parameters are denoted by { }
v.Add("GET", "/path/to/{param}", endpoint1)
v.Add("POST", "/path/to/{param}", endpoint1)
// Apply a regex check to the param by use of : followed by
// the regex.
v.AddHandler("PUT", "/path/to/{param: ^[0-9]+$}", endpoint2)
If a path contains extraneous symbols like extra /'s or .'s (barring trailing /'s), Verto will automatically
clean the path and send a redirect response to the cleaned path. By default, Verto will not attempt to redirect
paths with (without) trailing slashes if the other exists. Calling SetStrict(false)
on a Verto instance
lets Verto know that you want it to redirect trailing slashes.
Verto provides the option to chain middleware both globally and per route. Middleware must come in one of 3 forms:
- mux.PluginFunc
type PluginFunc(w http.ResponseWriter, r *http.Request, next http.Handler) func
- verto.PluginFunc
type PluginFunc(c *Context, next http.Handler) func
http.Handler
If the middleware comes in the form of an http.Handler
, next is automatically called.
// Example middleware usage
v := verto.New()
// Simple plugins
mw1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Hello, World 1!")
})
mw2 := mux.PluginFunc(func(w http.ResponseWriter, r *http.Request, next http.Handler) {
fmt.Println("Hello, World 2!")
next(w, r)
})
mw3 := verto.PluginFunc(func(c *verto.Context, next http.Handler) {
fmt.Println("Hello, World 3!")
next(w, r)
})
// Register global plugins: These will run for every single
// request. Middleware is served first come - first serve.
v.UseHandler(mw1)
v.UsePluginHandler(mw2)
v.Use(mw3)
// Register route
v.Add("GET", "/", func(c *verto.Context) (interface{}, error) {
fmt.Fprintf(c.Response, "Finished.")
})
v.Run()
Middleware can also be chained per route
// Register route and chain middle ware. These middleware
// will only be run for GET requests on /.
v.Add("GET", "/", func(c *verto.Context) (interface{}, error) {
...
}).UseHandler(mw1).UsePluginHandler(mw2).Use(mw3)
Verto provides the ability to create route groups.
// Create a group
g := v.Group("GET", "/path/path2/path3")
// Add endpoint handlers to a route group. The full path
// for the handler will be /path/path2/path3/handler.
g.Add("GET", "/handler", http.HandlerFunc(
func(w http.ResponseWriter, http.Request) {
fmt.Fprintf(w, "Hello!")
},
))
// Middleware can be chained per route group. Middleware
// will be run for all subgroups and handlers under the parent
// group.
g.Use(mux.PluginFunc(
func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
fmt.Fprintf(w, "World!")
},
))
// Create subgroups attached to a group. The resulting subgroup
// path will be /path/path2/path3/path4.
sub := g.Group("/path4")
Creating a group when there exists other groups/endpoint handlers that share a path prefix with the new group will cause those groups/endpoints to be subsumed under the new group. When creating groups, all wildcard segments are treated as equal. Thus, groups/endpoints subsumed under a new group with a wildcard segment in the shared prefix will use the new group's wildcard segment as key instead of their old segments. Attempting to create an already existing group returns the existing group.
Context is the custom parameter used for Verto's ResourceFunc
s.
It contains the original http.ResponseWriter
and *http.Request
as well as a reference to a logger and injections.
Context also provides a set of utility functions for dealing with parameter
setting and retrieval.
type Context struct {
// The original ResponseWriter and Request
Request *http.Request
Response http.ResponseWriter
// Logger
Logger Logger
// Injections
Injections *Injections
}
// Retrieves the first string value associated with the key.
func (c *Context) Get(key string) string { }
// Performs exactly like Get() but returns all values associated with the key.
func (c *Context) GetMulti(key string) []string { }
// Performs exactly like Get() but converts the value to a bool if possible.
// Throws an error if the conversion fails
func (c *Context) GetBool(key string) (bool, error) { }
// Does the same thing as GetBool() but converts to a float64 instead.
func (c *Context) GetFloat64(key string) (float64, error) { }
// Does the same thing as GetBool() but converts to an int64 instead.
func (c *Context) GetInt64(key string) (int64, error) { }
// Sets the value associated with key.
func (c *Context) Set(key, value string) { }
// Associated multiple values with the key.
func (c *Context) SetMulti(key string, values []string) { }
// Associates a boolean value with the key. Throws an error if there was a problem formatting value.
func (c *Context) SetBool(key string, value bool) error { }
// Associates a float64 value with the key. Throws an error if there was a problem formatting value.
func (c *Context) SetFloat64(key string, value float64) error { }
// Associates a int64 value with the key. Throws an error if there was a problem formatting value.
func (c *Context) SetInt64(key string, value int64) error { }
// The first call to Get or Set functions will attempt to parse the request query string
// and body for parameters so that they are available from the Get functions as well.
// Any errors encountered parsing can be retrieved using the ParseErrors function
func (c *Context) ParseErrors() error { }
Verto allows the user to define his own response handling function.
The handler must be of the form:
type ResponseHandler interface {
Handle(response interface{}, c *Context)
}
Verto also defines a ResponseFunc
to wrap functions so that
they implement ResponseHandler
. A default handler is provided
and used if no custom handler is provided. It is recommended that
the user brings his own handler as the default just attempts to
write response as is.
// Custom response handler example
handler := verto.ResponseFunc(func(response interface{}, c *verto.Context) {
// Attempt to marshal response as JSON before writing it
body, _ := json.Marshal(response)
fmt.Fprintf(c.Response, body)
})
v.RegisterResponseHandler(handler)
Like response handling, Verto allows the user to define his own error handler. The handler must be of the form:
type ErrorHandler interface {
Handle(err error, c *Context)
}
Verto also defines an ErrorFunc
to wrap functions so that they implement
ErrorHandler
. A default handler is provided but it is recommended that
the user brings his own handler. The default just responds with a 500 Internal Server Error
.
// Custom error handler example
handler := verto.ErrorFunc(func(err error, c *Context) {
fmt.Fprintf(c.Response, "Custom error response!")
})
v.RegisterErrorHandler(handler)
Injections are anything from the outside world you need passed to an endpoint
handler. A thread-local copy of the Injections struct is provided to each request
in order to support per-request lazy initialization of injections
NOTE: All Injections functions ARE thread safe.
// Injection example
// Inject using the Injections.Set() function. The first parameter
// is the key used to access then injection from within
// any handlers or plugins that implement verto.PluginFunc.
v.Injections.Set("key", "value")
// Lazily inject objects that have a high initialization cost
// The lifetime can be set to SINGLETON so that the object is only
// initialized once on the first call to Get/TryGet, or REQUEST so
// that the factory function is called per first call of Get/TryGet
// per request. The factory function provided to the lazy function
// takes in an interface ReadOnlyInjections that allows lazy injections
// to be instantiated based on other injected objects. ReadOnlyInjections
// only supports the Get/TryGet functions but otherwise works the same
// as Injections
v.Injections.Lazy("cache-client",
func(r verto.ReadOnlyInjections) interface{} {
return interface{} // example placeholder
}, verto.REQUEST)
// Inject anything like slices or structs
sl := make([]int64, 5)
st := &struct{
Value int64,
}{}
v.Injections.Set("slice", sl)
v.Injections.Set("struct", st)
// Retrieve values with Get()
st2 := v.Injections.Get("struct")
// TryGet() lets you check if the value exists
st3, exists := v.Injections.TryGet("struct")
// Delete() removes an injection.
v.Injections.Delete("slice")
// Clear clears all injections
v.Injections.Clear()
Injections are useful for things intricate loggers, analytics,
and caching.
Verto provides a default logger implementation but allows custom loggers that implement the following interface:
type Logger interface {
// The following functions print messages
// at various levels to any open log files and subscribers
Info(v ...interface{})
Debug(v ...interface{})
Warn(v ...interface{})
Error(v ...interface{})
Fatal(v ...interface{})
Panic(v ...interface{})
// The following functions print formatted messages
// at various levels to any open log files and subscribers
Infof(format string, v ...interface{})
Debugf(format string, v ...interface{})
Warnf(format string, v ...interface{})
Errorf(format string, v ...interface{})
Fatalf(format string, v ...interface{})
Panicf(format string, v ...interface{})
// Print a message to open log files and subscribers
Print(v ...interface{})
// Print a formatted message to open log files and subscribers
Printf(format string, v ...interface{})
// Close should dispose of any resources held by the logger
// (e.g. files, channels, etc.)
Close()
}