Nano is a simple & elegant HTTP multiplexer written in Go (Golang). It features REST API with Go net/http performance. If you need a minimalist, productivity, and love simplicity, Nano is great choice.
To install Nano package, you need to install Go and set your Go workspace first.
- The first need Go installed (version 1.11+ is required), then you can use the below Go command to install Nano.
go get -u github.com/hariadivicky/nano
- Import it in your code:
import "github.com/hariadivicky/nano"
- Import
net/http
. This is optional for http status code likehttp.StatusOK
.
import "net/http"
# assume the following codes in example.go file
$ cat example.go
package main
import (
"fmt"
"net/http"
"github.com/hariadivicky/nano"
)
func main() {
app := nano.New()
// simple endpoint to print hello world.
app.GET("/", func(c *nano.Context) {
c.String(http.StatusOK, "hello world\n")
})
app.Run(":8080")
}
# run example.go and visit http://localhost:8080 on your browser
$ go run example.go
You can find a ready-to-run examples at Todo List Example.
func main() {
// Creates a nano router
app := nano.New()
app.GET("/someGet", getHandler)
app.POST("/somePost", postHandler)
app.PUT("/somePut", putHandler)
app.DELETE("/someDelete", deleteHandler)
// Run apps.
app.Run(":8080")
}
You could register you own default handler, the default handler will called when there is not matching route found. If you doesn't set the default handler, nano will register default 404 response as default handler.
app.Default(func(c *nano.Context) {
c.JSON(http.StatusNotFound, nano.H{
"status": "error",
"message": "Oh no, this API endpoint does not exists."
})
})
Get route parameter using c.Param(key)
func main() {
app := nano.New()
// This handler will match /products/1 but will not match /products/ or /products
app.GET("/products/:productId", func(c *nano.Context) {
productId := c.Param("productId") //string
c.String(http.StatusOK, "you requested %s", productId)
})
}
To use request binding you must provide "form" or "json" tag to each field in your struct. You can also add rules:"required"
to marks a field as required for binding validation
type Address struct {
Street string `form:"street" json:"street"`
PostalCode string `form:"postal_code" json:"postal_code"`
CityID int `form:"city_id" json:"city_id" rules:"required"`
}
By calling c.Bind
function, it's will returns *nano.BindingError
when an error occured due to deserialization error or validation error
app.GET("/address", func(c *nano.Context) {
var address Address
if err := c.Bind(&address); err != nil {
c.String(err.HTTPStatusCode, err.Message)
return
}
c.String(http.StatusOK, "city id: %d, postal code: %s", address.CityID, address.PostalCode)
})
c.Bind
automatically choose deserialization source based on your request Content-Type and request method. GET
and HEAD
methods will try to bind url query or urlencoded form. Otherwise, it will try to bind multipart form or json.
but if you want to manually choose the binding source, you could uses this functions below:
If you want to bind url query like page=1&limit=50
or urlencoded form you could use nano.BindSimpleForm
var paging Pagination
err := nano.BindSimpleForm(c.Request, &paging)
You could use nano.BindMultipartForm
to bind request body with multipart/form-data
type
var post BlogPost
err := nano.BindMultipartForm(c.Request, &post)
if you have request with application/json
type, you could bind it using nano.BindJSON
function
var schema ComplexSchema
err := nano.BindJSON(c.Request, &schema)
Each you call Bind
, BindSimpleForm
, BindMultipartForm
, and BindJSON
it's always returns *nano.ErrorBinding,
except when binding success without any errors it returns nil
. ErrorBinding has two field that are HTTPStatusCode & Message. Here is the details:
HTTPStatusCode | Reason | |
---|---|---|
1 | 500 | Conversion Error or Give non-pointer to target struct parameter |
2 | 420 | Validation Error |
3 | 400 | Deserialization Error |
ErrorBinding.HTTPStatusCode
is useful to determine response code
You could use grouping routes that have same prefix or using same middlewares
app := nano.New()
// simple endpoint to print hello world.
app.GET("/", func(c *nano.Context) {
c.String(http.StatusOK, "hello world\n")
})
// path: /api/v1
apiv1 := app.Group("/api/v1")
{
// path: /api/v1/finances
finance := apiv1.Group("/finances")
{
// path: /api/v1/finances/report/1
finance.GET("/report/:period", reportByPeriodHandler)
// path: /api/v1/finances/expenses/voucher
finance.POST("/expenses/voucher", createExpenseVoucherHandler)
}
// path: /api/v1/users
users := apiv1.Group("/users")
{
// path: /api/v1/users
users.POST("/", registerNewUserHandler)
// path: /api/v1/users/1/detail
users.GET("/:id/detail", showUserDetailHandler)
}
}
Middleware implement nano.HandlerFunc, you could forward request by calling c.Next()
// LoggerMiddleware functions to log every request.
func LoggerMiddleware() nano.HandlerFunc {
return func(c *nano.Context) {
// before middleware.
start := time.Now()
log.Println(c.Method, c.Path)
// forward to next handler.
c.Next()
// after middleware.
log.Printf("request complete in %s", time.Since(start))
}
}
Using middleware on certain route
app.GET("/change-password", verifyTokenMiddleware(), changePasswordHandler)
You could chaining middleware or handler
app.GET("/secret", verifyStepOne(), verifyStepTwo(), grantAccessHandler, logChangeHandler)
Using middleware on router group
app := nano.New()
// apply to all routes.
app.Use(globalMiddleware())
v1 := app.Group("/v1")
v1.Use(onlyForV1())
{
// will apply to v1 routes.
}
Nano Context is wrapper for http request and response. this example will use c
variable as type of *nano.Context
Get request method
& path
log.Println(c.Method, c.Path)
Get field value from request body
username := c.PostForm("username")
Get field value with default value from request body
status := c.PostFormDefault("status", "active")
Get url query
page := c.Query("page")
Get url query with default value
page := c.QueryDefault("page", "1")
You could check if client need JSON response
func main() {
app := nano.New()
// simple endpoint to print hello world.
app.GET("/", func(c *nano.Context) {
// client request json response.
if c.ExpectJSON() {
c.JSON(http.StatusOK, nano.H{
"message": "hello world",
})
return
}
c.String(http.StatusOK, "hello world\n")
})
app.Run(":8080")
}
Parsing json body request
type person struct {
Name string
Age int
}
func main() {
app := nano.New()
// simple endpoint to print hello world.
app.POST("/person", func(c *nano.Context) {
if !c.IsJSON() {
c.String(http.StatusBadRequest, "server only accept json request.")
}
form := new(person)
// parse json body.
err := c.ParseJSONBody(form)
if err != nil {
c.String(http.StatusBadRequest, "bad request.")
}
c.String(http.StatusOK, "hello %s your age is %d \n", form.Name, form.Age)
})
app.Run(":8080")
}
Set response header & content type
c.SetHeader("X-Powered-By", "Nano HTTP Multiplexer")
c.SetContetType("image/png")
Plain text response
c.String(http.StatusNotFound, "404 not found: %s", c.Path)
JSON response (with nano object wrapper)
c.JSON(http.StatusOK, nano.H{
"status": "pending",
"message": "data stored",
})
HMTL response
c.HTML(http.StatusOK, "<h1>Hello There!</h1>")
Binary response
c.Data(http.StatusOK, binaryData)
Nano has shipped with some default middleware like cors and recovery middleware.
Recovery middleware is functions to recover server when panic was fired.
func main() {
app := nano.New()
app.Use(nano.Recovery())
app.GET("/", func(c *nano.Context) {
stack := make([]string, 0)
c.String(http.StatusOK, "100th stack is %s", stack[99])
})
app.Run(":8080")
}
This middleware is used to deal with cross-origin request.
func main() {
app := nano.New()
// Only allow from :3000 and google.
cors := nano.CORSWithConfig(nano.CORSConfig{
AllowedOrigins: []string{"http://localhost:3000", "https://wwww.google.com"},
AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut},
AllowedHeaders: []string{nano.HeaderContentType, nano.HeaderAccept},
})
app.Use(cors)
// ...
}
Gzip middleware is used for http response compression.
func main() {
app := nano.New()
app.Use(nano.Gzip(gzip.DefaultCompression))
// ...
}
don't forget to import compress/gzip
package for compression level at this example. available compression levels are: gzip.NoCompression
, gzip.BestSpeed
, gzip.BestCompression
, gzip.DefaultCompression
, and gzip.HuffmanOnly
Awesome project lists using Nano web framework.
- Coming soon: A local retail shop written in Go.
Nano using MIT license. Please read LICENSE files for more information about nano license.