YARF is a fast micro-framework designed to build REST APIs and web services in a fast and simple way. Designed after Go's composition features, takes a new approach to write simple and DRY code.
Here's a transcription from our examples/simple package. This is a very simple Hello World web application example.
package main
import (
"github.com/yarf-framework/yarf"
)
// Define a simple resource
type Hello struct {
yarf.Resource
}
// Implement the GET method
func (h *Hello) Get(c *yarf.Context) error {
c.Render("Hello world!")
return nil
}
// Run app server on http://localhost:8080
func main() {
y := yarf.New()
y.Add("/", new(Hello))
y.Start(":8080")
}
For more code and examples demonstrating all YARF features, please refer to the 'examples' directory.
YARF resources are custom structs that act as handlers for HTTP methods. Each resource can implement one, several or all HTTP methods needed (GET, POST, DELETE, etc.). Resources are created using Go's struct composition and you only have to declare the yarf.Resource type into your own struct.
Example:
import "github.com/yarf-framework/yarf"
// Define a simple resource
type Hello struct {
yarf.Resource
}
// Implement the GET method
func (h *Hello) Get(c *yarf.Context) error {
c.Render("Hello world!")
return nil
}
Using a strict match model, it matches exact URLs against resources for increased performance and clarity during routing. The routes supports parameters in the form '/:param'.
The route:
/hello/:name
Will match:
/hello/Joe
/hello/nobody
/hello/somebody/
/hello/:name
/hello/etc
But it won't match:
/
/hello
/hello/Joe/AndMark
/Joe/:name
/any/thing
You can define optional parameters using multiple routes on the same Resource.
At this point you know how to define parameters in your routes using the /:param naming convention. Now you'll see how easy is to get these parameters by their name from your resources using the Context.Param() method.
Example:
For the route:
/hello/:name
You can have this resource:
import "github.com/yarf-framework/yarf"
type Hello struct {
yarf.Resource
}
func (h *Hello) Get(c *yarf.Context) error {
name := c.Param("name")
c.Render("Hello, " + name)
return nil
}
When some extra freedom is needed on your routes, you can use a *
as part of your routes to match anything where the wildcard is present.
The route:
/something/*/here
Will match the routes
/something/is/here
/something/happen/here
/something/isnt/here
/something/was/here
And so on...
You can also combine this with parameters inside the routes for extra complexity.
When using the *
at the end of any route, the router will match everything from the wildcard and forward.
The route:
/match/from/here/*
Will match:
/match/from/here
/match/from/here/please
/match/from/here/and/forward
/match/from/here/and/forward/for/ever/and/ever
And so on...
The *
can only be used by itself and it doesn't works for single character matching like in regex.
So the route:
/match/some*
Will NOT match:
/match/some
/match/something
/match/someone
/match/some/please
The Context object is passed as a parameter to all Resource methods and contains all the information related to the ongoing request.
Check the Context docs for a reference of the object: https://godoc.org/github.com/yarf-framework/yarf#Context
Middleware support is implemented in a similar way as Resources, by using composition.
Routes will be pre-filtered and post-filtered by Middleware handlers when they're inserted in the router.
Example:
import "github.com/yarf-framework/yarf"
// Define your middleware and composite yarf.Middleware
type HelloMiddleware struct {
yarf.Middleware
}
// Implement only the PreDispatch method, PostDispatch not needed.
func (m *HelloMiddleware) PreDispatch(c *yarf.Context) error {
c.Render("Hello from middleware! \n") // Render to response.
return nil
}
// Insert your middlewares to the server
func main() {
y := yarf.New()
// Add middleware
y.Insert(new(HelloMiddleware))
// Define routes
// ...
// ...
// Start the server
y.Start()
}
Routes can be grouped into a route prefix and handle their own middleware.
As routes can be grouped into a route prefix, other groups can be also grouped allowing for nested prefixes and middleware layers.
Example:
import "github.com/yarf-framework/yarf"
// Entry point of the executable application
// It runs a default server listening on http://localhost:8080
//
// URLs after configuration:
// http://localhost:8080
// http://localhost:8080/hello/:name
// http://localhost:8080/v2
// http://localhost:8080/v2/hello/:name
// http://localhost:8080/extra/v2
// http://localhost:8080/extra/v2/hello/:name
//
func main() {
// Create a new empty YARF server
y := yarf.New()
// Create resources
hello := new(Hello)
hellov2 := new(HelloV2)
// Add main resource to multiple routes at root level.
y.Add("/", hello)
y.Add("/hello/:name", hello)
// Create /v2 prefix route group
g := yarf.RouteGroup("/v2")
// Add /v2/ routes to the group
g.Add("/", hellov2)
g.Add("/hello/:name", hellov2)
// Use middleware only on the /v2/ group
g.Insert(new(HelloMiddleware))
// Add group to Yarf routes
y.AddGroup(g)
// Create another group for nesting into it.
n := yarf.RouteGroup("/extra")
// Nest /v2 group into /extra/v2
n.AddGroup(g)
// Use another middleware only for this /extra/v2 group
n.Insert(new(ExtraMiddleware))
// Add group to Yarf
y.AddGroup(n)
// Start server listening on port 8080
y.Start(":8080")
}
Check the ./examples/routegroups demo for the complete working implementation.
A route cache is enabled by default to improve dispatch speed, but sacrificing memory space. If you're running out of RAM memory and/or your app has too many possible routes that may not fit, you should disable the route cache.
To enable/disable the route cache, just set the UseCache flag of the Yarf object:
y := yarf.New()
y.UseCache = false
Just use the Yarf object as any http.Handler on a chain. Set another http.Handler on the Yarf.Follow property to be followed in case this Yarf router can't match the request.
Here's an example on how to follow the request to a public file server:
package main
import (
"github.com/yarf-framework/yarf"
"net/http"
)
func main() {
y := yarf.New()
// Add some routes
y.Add("/hello/:name", new(Hello))
//... more routes here
// Follow to file server
y.Follow = http.FileServer(http.Dir("/var/www/public"))
// Start the server
y.Start(":8080")
}
You can handle all 404 errors returned by any resource/middleware during the request flow of a Yarf server. To do so, you only have to implement a function with the func(c *yarf.Context) signature and set it to your server's Yarf.NotFound property.
y := yarf.New()
// ...
y.NotFound = func(c *yarf.Context) {
c.Render("This is a custom Not Found handler")
}
// ...
On initial benchmarks, the framework seems to perform very well compared with other similar frameworks. Even when there are faster frameworks, under high load conditions and thanks to the route caching method, YARF seems to perform as good or even better than the fastests that work better under simpler conditions.
Check the benchmarks repository to run your own:
https://github.com/yarf-framework/benchmarks
Support for running HTTPS server from the net/http package.
func main() {
y := yarf.New()
// Setup the app
// ...
// ...
// Start https listening on port 443
y.StartTLS(":443", certFile, keyFile)
}
func main() {
y := yarf.New()
// Setup the app
// ...
// ...
// Configure custom http server and set the yarf object as Handler.
s := &http.Server{
Addr: ":443",
Handler: y,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
s.ListenAndServeTLS(certFile, keyFile)
}
Why not?
No, seriously, i've researched for small/fast frameworks in the past for a Go project that I was starting. I found several options, but at the same time, none seemed to suit me. Some of them make you write weird function wrappers to fit the net/http package style. Actually, most of them seem to be function-based handlers. While that's not wrong, I feel more comfortable with the resource-based design, and this I also feel aligns better with the spirit of REST.
In Yarf you create a resource struct that represents a REST resource and it has all HTTP methods available. No need to create different routes for GET/POST/DELETE/etc. methods.
By using composition, you don't need to wrap functions inside functions over and over again to implement simple things like middleware or extension to your methods. You can abuse composition to create a huge OO-like design for your business model without sacrifying performance and code readability.
Even while the code style differs from the net/http package, the framework is fully compatible with it and couldn't run without it. Extensions and utilities from other frameworks or even the net/http package can be easily implemented into Yarf by just wraping them up into a Resource, just as you would do on any other framework by wrapping functions.
Context handling also shows some weird designs across frameworks. Some of them rely on reflection to receive any kind of handlers and context types. Others make you receive some extra parameter in the handler function that actually brokes the net/http compatibility, and you have to carry that context parameter through all middleware/handler-wrapper functions just to make it available. In Yarf, the Context is automatically sent as a parameter to all Resource methods by the framework.
For all the reasons above, among some others, there is a new framework in town.