Nano is a simple & elegant HTTP request 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
Nano is 100% compatible with standard go net/http
package, you can also attach nano as http.Server
handler.
app := nano.New()
// ...
server := &http.Server{
WriteTimeout: 10 * time.Second,
ReadTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second,
Handler: app, // attach Nano as server handler.
Addr: ":8000",
}
server.ListenAndServe()
You can find a ready-to-run examples at Todo List Example.
func main() {
// Creates nano instance.
app := nano.New()
app.HEAD("/someHead", headHandler)
app.OPTIONS("/someOptions", optionsHandler)
app.GET("/someGet", getHandler)
app.POST("/somePost", postHandler)
app.PUT("/somePut", putHandler)
app.PATCH("/somePatch", patchHandler)
app.DELETE("/someDelete", deleteHandler)
// Run apps.
app.Run(":8080")
}
You can register your own default handler. The default handler called when there is no matching route. If you doesn't set the default handler, nano will register 404 response text 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)
})
}
You can use *nano.Static()
function to serve static files like html, css, and js in your server.
func main() {
app := nano.New()
assetDir := http.Dir("./statics")
app.Static("/assets", assetDir)
// now your static files are accessible via http://yourhost.com/assets/*
}
To use request binding you must provide form
tag to each field in your struct. You can also add the validation rules using validate
tag. to see more about available validate
tag value, visit Go Validator
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" validate:"required"`
}
Bind
function will returns *nano.BindingError
when an error occured due to deserialization error or validation error. The description about error fields will be stored in err.Fields
.
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)
})
Bind
function 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 can use this functions:
If you want to bind url query like page=1&limit=50
or urlencoded form you can use BindSimpleForm
var paging Pagination
err := c.BindSimpleForm(&paging)
You can use BindMultipartForm
to bind request body with multipart/form-data
type
var post BlogPost
err := c.BindMultipartForm(&post)
if you have request with application/json
type, you can bind it using BindJSON
function
var cart ShoppingCart
err := c.BindJSON(&cart)
BindJSON
can also parsing your RFC3339
date/time format to another format by adding time_format
in your field tag. You can read more at jsontime docs.
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 which 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 can make routes grouping which it 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 implements nano.HandlerFunc, you can forward the request to next handler 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 in certain route
app.GET("/change-password", verifyTokenMiddleware(), changePasswordHandler)
You can chains the middlewares (handler)
app.GET("/secret", verifyStepOne(), verifyStepTwo(), grantAccessHandler, logChangeHandler)
Using middleware in 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")
}
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 (using 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, gzip compressor, and recovery middleware.
Recovery middleware recovers server from panic.
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")
}
CORS middleware handles 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 compresses http response using gzip encoding.
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 projects list 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.