looli is a minimalist web framework for golang.
go get github.com/cssivision/loolilooli build on the top of router library, which support Named parameters Wildcard parameters Trailing slash redirect Case sensitive Prefix router, for detail.
package main
import (
	"github.com/cssivision/looli"
	"log"
)
func main() {
	router := looli.Default()
	router.Get("/a", func(c *looli.Context) {})
	router.Post("/a", func(c *looli.Context) {})
	router.Put("/a", func(c *looli.Context) {})
	router.Delete("/a", func(c *looli.Context) {})
	router.Patch("/a", func(c *looli.Context) {})
	router.Head("/a", func(c *looli.Context) {})
	router.Options("/a", func(c *looli.Context) {})
	log.Fatal(router.Run(":8080"))
}Named parameters only match a single path segment:
Pattern: /user/:name
 /user/gordon              match
 /user/you                 match
 /user/gordon/profile      no match
 /user/                    no match
Pattern: /:user/:name
 /a/gordon                 match
 /b/you                    match
 /user/gordon/profile      no match
 /user/                    no match
package main
import (
    "net/http"
    "github.com/cssivision/looli"
)
func main() {
    router := looli.Default()
    router.Get("/a/:name", func(c *looli.Context) {
        c.Status(200)
        c.String("hello " + c.Param("name") + "!\n")
    })
    http.ListenAndServe(":8080", router)
}Match everything, therefore they must always be at the end of the pattern:
Pattern: /src/*filepath
 /src/                     match
 /src/somefile.go          match
 /src/subdir/somefile.go   match
package main
import (
    "net/http"
    "github.com/cssivision/looli"
)
func main() {
    router := looli.Default()
    router.Get("/a/*filepath", func(c *looli.Context) {
        c.Status(200)
        c.String("hello " + c.Param("filepath") + "!\n")
    })
    http.ListenAndServe(":8080", router)
}By default will redirect, which means if we register path /a/b, we can request with /a/b/, conversely also success. redirect will work only in the situation that the request can not found, if both define path /a/b and /a/b/, redirect will not work.
/a/b -> /a/b/
/a/b/ -> /a/b
package main
import (
    "net/http"
    "github.com/cssivision/looli"
)
func main() {
    router := looli.Default()
    // default is true, we can forbidden this behavior by set is to false
    // request with /a/ will get 404
    router.SetTrailingSlashRedirect(false)
    router.Get("/a", func(c *looli.Context) {
        c.Status(200)
        c.String("hello world!\n")
    })
    http.ListenAndServe(":8080", router)
}By default is not case sensitive, which means if we register path /a/b, request with /A/B will get 404 not found. if we set true, request path with /A/B will success.
package main
import (
    "net/http"
    "github.com/cssivision/looli"
)
func main() {
    router := looli.Default()
    // default is false, we can forbidden this behavior by set is to true
    // request with /A/ will success.
    router.SetIgnoreCase(true)
    router.Get("/a", func(c *looli.Context) {
        c.Status(200)
        c.String("hello world!\n")
    })
    http.ListenAndServe(":8080", router)
}Group router using prefix
package main
import (
    "net/http"
    "github.com/cssivision/looli"
)
func main() {
    router := looli.Default()
    v1 := router.Prefix("/v1")
    v1.Get("/a", func(c *looli.Context) {
        c.Status(200)
        c.String("hello world version1\n")
    })
    v2 := router.Prefix("/v2")
    v2.Get("/a", func(c *looli.Context) {
        c.Status(200)
        c.String("hello world version2\n")
    })
    router.Get("/a", func(c *looli.Context) {
        c.Status(200)
        c.String("hello world!\n")
    })
    http.ListenAndServe(":8080", router)
}package main
import (
    "net/http"
    "github.com/cssivision/looli"
)
func main() {
    router := looli.Default()
    // Serve file in the path
    router.StaticFile("/somefile.go", "file/path")
    // Serve files in staic directory
    router.Static("/static", "./static")
    http.ListenAndServe(":8080", router)
}Context supply some syntactic sugar.
package main
import (
    "net/http"
    "github.com/cssivision/looli"
)
func main() {
    router := looli.Default()
    router.Get("/query", func(c *looli.Context) {
        id := c.Query("id")
        name := c.DefaultQuery("name", "cssivision")
        c.Status(200)
        c.String("hello %s, %s\n", id, name)
    })
    router.Post("/form", func(c *looli.Context) {
        name := c.DefaultPostForm("name", "somebody")
        age := c.PostForm("age")
        c.Status(200)
        c.JSON(looli.JSON{
            "name": name,
            "age": age,
        })
    })
    http.ListenAndServe(":8080", router)
}query
curl 'localhost:8080/query?id=1&name=cssivision'form
curl -d 'age=21&other=haha' 'localhost:8080/form?id=1&name=cssivision'Use method to operate header and cookie
package main
import (
    "fmt"
    "github.com/cssivision/looli"
    "log"
    "net/http"
)
func main() {
    router := looli.Default()
    router.Get("/header", func(c *looli.Context) {
        fmt.Println(c.Header("User-Agent"))
        c.SetHeader("fake-header", "fake")
        c.Status(200)
        c.String("fake header has setted\n")
    })
    router.Get("/cookie", func(c *looli.Context) {
        val, _ := c.Cookie("fake-cookie")
        fmt.Println(val)
        c.SetCookie(&http.Cookie{
            Name: "fake-cookie",
            Value: "fake",
        })
        c.Status(200)
        c.String("fake cookie has setted\n")
    })
    log.Fatal(router.Run(":8080"))
}To bind a request into a type, use data binding, data can from query, post body. currently support binding of JSON, XML and standard form values (x-www-form-urlencoded and multipart/form-data). When using the Bind-method, the binder depending on the Content-Type header.
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set json:"fieldname".
The Validation of the incoming data show below.
package main
import (
    "fmt"
    "github.com/cssivision/looli"
    "net/http"
)
type Infomation struct {
    Name string`json:"name"`
    Age int`json:"age"`
}
func (i *Infomation) Validate() error {
    // data validate,
    return nil
}
func main() {
    router := looli.Default()
    // curl 'localhost:8080/query?name=cssivision&age=21'
    router.Get("/query", func(c *looli.Context) {
        query := new(Infomation)
        if err := c.Bind(query); err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println(query.Name)
        fmt.Println(query.Age)
        c.Status(200)
        c.JSON(query)
    })
    // curl -d "name=cssivision&age=21" 'localhost:8080/form'
    router.Post("/form", func(c *looli.Context) {
        form := new(Infomation)
        if err := c.Bind(form); err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println(form.Name)
        fmt.Println(form.Age)
        c.Status(200)
        c.JSON(form)
    })
    // curl  -H "Content-Type: application/json" -X POST -d '{"name":"cssivision","age":21}' localhost:8080/json
    router.Post("/json", func(c *looli.Context) {
        json := new(Infomation)
        if err := c.Bind(json); err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println(json.Name)
        fmt.Println(json.Age)
        c.Status(200)
        c.JSON(json)
    })
    http.ListenAndServe(":8080", router)
}package main
import (
    "github.com/cssivision/looli"
    "net/http"
)
func main() {
    router := looli.Default()
    router.Get("/string", func(c *looli.Context) {
        c.String("the response is %s\n", "string")
    })
    router.Get("/json1", func(c *looli.Context) {
        c.JSON(looli.JSON{
            "name": "cssivision",
            "age": 21,
        })
    })
    router.Get("/json2", func(c *looli.Context) {
        var msg struct {
            Name string`json:"name"`
            Age int`json:"age"`
        }
        msg.Name = "cssivision"
        msg.Age = 21
        c.JSON(msg)
    })
    http.ListenAndServe(":8080", router)
}package main
import (
    "github.com/cssivision/looli"
    "net/http"
)
func main() {
    router := looli.Default()
    router.LoadHTMLGlob("templates/*")
    router.Get("/html", func(c *looli.Context) {
        c.HTML("index.tmpl", looli.JSON{
            "title": "my site",
        })
    })
    http.ListenAndServe(":8080", router)
}templates/index.tmpl
<html>
    <h1>
        {{ .title }}
    </h1>
</html>looli.Default() with middleware Logger() Recover() by default, without middleware use looli.New() instead.
package main
import (
    "net/http"
    "github.com/cssivision/looli"
    "log"
)
func main() {
    router := looli.New()
    // global middleware
    router.Use(looli.Logger())
    router.Get("/a", func(c *looli.Context) {
        c.Status(200)
        c.String("hello world!\n")
    })
    // multi handler for specificed path
    router.Get("/b", func(c *looli.Context) {
        c.String("first handler\n")
    }, func(c *looli.Context) {
        c.String("second handler\n")
    })
    v1 := router.Prefix("/v1")
    // recover middleware only work for /v1 prefix router
    v1.Use(looli.Recover())
    v1.Get("/a", func(c *looli.Context) {
        panic("error!")
        c.Status(200)
        c.String("hello world!\n")
    })
    log.Fatal(http.ListenAndServe(":8080", router))
}- Logger middleware
 - Recover middleware
 - Session middleware
 - Cors middleware
 - Csrf middleware
 
package main
import (
    "log"
    "net/http"
    "github.com/cssivision/looli"
    "time"
)
func Logger() looli.HandlerFunc {
    return func(c *looli.Context) {
        t := time.Now()
        // before request
        c.Next()
        // after request
        latency := time.Since(t)
        log.Print(latency)
    }
}
func main() {
    router := looli.New()
    // global middleware
    router.Use(Logger())
    router.Get("/a", func(c *looli.Context) {
        c.Status(200)
        c.String("hello world!\n")
    })
    http.ListenAndServe(":8080", router)
}All source code is licensed under the MIT License.
- elegant error handle
 
