{"id":13756117,"url":"https://github.com/cssivision/looli","last_synced_at":"2025-06-20T10:35:23.151Z","repository":{"id":144202862,"uuid":"81708296","full_name":"cssivision/looli","owner":"cssivision","description":"a tiny web framework","archived":false,"fork":false,"pushed_at":"2020-05-24T14:23:00.000Z","size":194,"stargazers_count":44,"open_issues_count":2,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-08T05:10:40.655Z","etag":null,"topics":["framework","go","looli","middleware"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cssivision.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-02-12T06:45:01.000Z","updated_at":"2025-03-10T04:12:56.000Z","dependencies_parsed_at":"2023-06-18T22:01:14.021Z","dependency_job_id":null,"html_url":"https://github.com/cssivision/looli","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/cssivision/looli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cssivision%2Flooli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cssivision%2Flooli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cssivision%2Flooli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cssivision%2Flooli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cssivision","download_url":"https://codeload.github.com/cssivision/looli/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cssivision%2Flooli/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260927234,"owners_count":23083982,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","go","looli","middleware"],"created_at":"2024-08-03T11:00:36.815Z","updated_at":"2025-06-20T10:35:18.137Z","avatar_url":"https://github.com/cssivision.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# looli\n\u003cimg align=\"right\" width=\"120px\" src=\"https://raw.githubusercontent.com/cssivision/looli/master/looli.jpeg\"\u003e\n\n[![Build Status](https://img.shields.io/travis/cssivision/looli.svg?style=flat-square)](https://travis-ci.org/cssivision/looli)\n[![Coverage Status](http://img.shields.io/coveralls/cssivision/looli.svg?style=flat-square)](https://coveralls.io/github/cssivision/looli?branch=master)\n[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://github.com/cssivision/looli/blob/master/LICENSE)\n\n\nlooli is a minimalist web framework for golang.\n\n# Feature\n\n* [Router](#router)\n    * [full method support](#using-get-post-put-patch-delete-and-options)\n    * [named parameter](#named-parameter)\n    * [wildcard pattern](#wildcard-pattern)\n    * [trailing slash redirect](#trailing-slash-redirect)\n    * [case sensitive](#case-sensitive)\n    * [serving static files](#serving-static-files)\n* [Context](#context)\n    * [query and form](#query-and-form)\n    * [header and cookie](#header-and-cookie)\n    * [data binding](#data-binding)\n    * [string xml json rendering](#string-json-rendering)\n    * [html rendering](#html-rendering)\n* [Middleware](#middleware)\n    * [using middleware](#using-middleware)\n    * [builtin middlewares](#builtin-middlewares)\n    * [custome middleware](#custome-middleware)\n\n# Installation\n\n```sh\ngo get github.com/cssivision/looli\n```\n\n# Usage\n\n## Router\n\nlooli build on the top of [router](https://github.com/cssivision/router) library, which support `Named parameters` `Wildcard parameters` `Trailing slash redirect` `Case sensitive` `Prefix router`, for [detail](https://github.com/cssivision/router).\n\n### Using GET, POST, PUT, PATCH, DELETE and OPTIONS\n\n```go\npackage main\n\nimport (\n\t\"github.com/cssivision/looli\"\n\t\"log\"\n)\n\nfunc main() {\n\trouter := looli.Default()\n\n\trouter.Get(\"/a\", func(c *looli.Context) {})\n\trouter.Post(\"/a\", func(c *looli.Context) {})\n\trouter.Put(\"/a\", func(c *looli.Context) {})\n\trouter.Delete(\"/a\", func(c *looli.Context) {})\n\trouter.Patch(\"/a\", func(c *looli.Context) {})\n\trouter.Head(\"/a\", func(c *looli.Context) {})\n\trouter.Options(\"/a\", func(c *looli.Context) {})\n\n\tlog.Fatal(router.Run(\":8080\"))\n}\n\n```\n\n### Named parameter\n\nNamed parameters only match a single path segment:\n```\nPattern: /user/:name\n\n /user/gordon              match\n /user/you                 match\n /user/gordon/profile      no match\n /user/                    no match\n\nPattern: /:user/:name\n\n /a/gordon                 match\n /b/you                    match\n /user/gordon/profile      no match\n /user/                    no match\n```\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    router.Get(\"/a/:name\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello \" + c.Param(\"name\") + \"!\\n\")\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n### Wildcard pattern\n\nMatch everything, therefore they must always be at the end of the pattern:\n\n```\nPattern: /src/*filepath\n\n /src/                     match\n /src/somefile.go          match\n /src/subdir/somefile.go   match\n```\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    router.Get(\"/a/*filepath\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello \" + c.Param(\"filepath\") + \"!\\n\")\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n### Trailing slash redirect\n\nBy 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.\n\n```\n/a/b -\u003e /a/b/\n/a/b/ -\u003e /a/b\n```\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    // default is true, we can forbidden this behavior by set is to false\n    // request with /a/ will get 404\n    router.SetTrailingSlashRedirect(false)\n\n    router.Get(\"/a\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello world!\\n\")\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n### Case sensitive\n\nBy 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.\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    // default is false, we can forbidden this behavior by set is to true\n    // request with /A/ will success.\n    router.SetIgnoreCase(true)\n\n    router.Get(\"/a\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello world!\\n\")\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n### Prefix router\n\nGroup router using prefix\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    v1 := router.Prefix(\"/v1\")\n    v1.Get(\"/a\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello world version1\\n\")\n    })\n\n    v2 := router.Prefix(\"/v2\")\n    v2.Get(\"/a\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello world version2\\n\")\n    })\n\n    router.Get(\"/a\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello world!\\n\")\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n### Serving static files\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    // Serve file in the path\n    router.StaticFile(\"/somefile.go\", \"file/path\")\n\n    // Serve files in staic directory\n    router.Static(\"/static\", \"./static\")\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n## Context\n\nContext supply some syntactic sugar.\n\n### Query and Form\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    router.Get(\"/query\", func(c *looli.Context) {\n        id := c.Query(\"id\")\n        name := c.DefaultQuery(\"name\", \"cssivision\")\n        c.Status(200)\n        c.String(\"hello %s, %s\\n\", id, name)\n    })\n\n    router.Post(\"/form\", func(c *looli.Context) {\n        name := c.DefaultPostForm(\"name\", \"somebody\")\n        age := c.PostForm(\"age\")\n        c.Status(200)\n        c.JSON(looli.JSON{\n            \"name\": name,\n            \"age\": age,\n        })\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\nquery\n```sh\ncurl 'localhost:8080/query?id=1\u0026name=cssivision'\n```\n\nform\n```sh\ncurl -d 'age=21\u0026other=haha' 'localhost:8080/form?id=1\u0026name=cssivision'\n```\n\n### Header and Cookie\n\nUse method to operate header and cookie\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/cssivision/looli\"\n    \"log\"\n    \"net/http\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    router.Get(\"/header\", func(c *looli.Context) {\n        fmt.Println(c.Header(\"User-Agent\"))\n        c.SetHeader(\"fake-header\", \"fake\")\n        c.Status(200)\n        c.String(\"fake header has setted\\n\")\n    })\n\n    router.Get(\"/cookie\", func(c *looli.Context) {\n        val, _ := c.Cookie(\"fake-cookie\")\n        fmt.Println(val)\n        c.SetCookie(\u0026http.Cookie{\n            Name: \"fake-cookie\",\n            Value: \"fake\",\n        })\n        c.Status(200)\n        c.String(\"fake cookie has setted\\n\")\n    })\n\n    log.Fatal(router.Run(\":8080\"))\n}\n```\n\n### Data binding\n\nTo 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.\n\nNote 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\".\n\nThe Validation of the incoming data show below.\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/cssivision/looli\"\n    \"net/http\"\n)\n\ntype Infomation struct {\n    Name string`json:\"name\"`\n    Age int`json:\"age\"`\n}\n\nfunc (i *Infomation) Validate() error {\n    // data validate，\n    return nil\n}\n\nfunc main() {\n    router := looli.Default()\n\n    // curl 'localhost:8080/query?name=cssivision\u0026age=21'\n    router.Get(\"/query\", func(c *looli.Context) {\n        query := new(Infomation)\n        if err := c.Bind(query); err != nil {\n            fmt.Println(err)\n            return\n        }\n        fmt.Println(query.Name)\n        fmt.Println(query.Age)\n        c.Status(200)\n        c.JSON(query)\n    })\n\n    // curl -d \"name=cssivision\u0026age=21\" 'localhost:8080/form'\n    router.Post(\"/form\", func(c *looli.Context) {\n        form := new(Infomation)\n        if err := c.Bind(form); err != nil {\n            fmt.Println(err)\n            return\n        }\n        fmt.Println(form.Name)\n        fmt.Println(form.Age)\n        c.Status(200)\n        c.JSON(form)\n    })\n\n    // curl  -H \"Content-Type: application/json\" -X POST -d '{\"name\":\"cssivision\",\"age\":21}' localhost:8080/json\n    router.Post(\"/json\", func(c *looli.Context) {\n        json := new(Infomation)\n        if err := c.Bind(json); err != nil {\n            fmt.Println(err)\n            return\n        }\n        fmt.Println(json.Name)\n        fmt.Println(json.Age)\n        c.Status(200)\n        c.JSON(json)\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n### String JSON rendering\n\n```go\npackage main\n\nimport (\n    \"github.com/cssivision/looli\"\n    \"net/http\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    router.Get(\"/string\", func(c *looli.Context) {\n        c.String(\"the response is %s\\n\", \"string\")\n    })\n\n    router.Get(\"/json1\", func(c *looli.Context) {\n        c.JSON(looli.JSON{\n            \"name\": \"cssivision\",\n            \"age\": 21,\n        })\n    })\n\n    router.Get(\"/json2\", func(c *looli.Context) {\n        var msg struct {\n            Name string`json:\"name\"`\n            Age int`json:\"age\"`\n        }\n\n        msg.Name = \"cssivision\"\n        msg.Age = 21\n\n        c.JSON(msg)\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n### HTML rendering\n\n```go\npackage main\n\nimport (\n    \"github.com/cssivision/looli\"\n    \"net/http\"\n)\n\nfunc main() {\n    router := looli.Default()\n\n    router.LoadHTMLGlob(\"templates/*\")\n    router.Get(\"/html\", func(c *looli.Context) {\n        c.HTML(\"index.tmpl\", looli.JSON{\n            \"title\": \"my site\",\n        })\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\ntemplates/index.tmpl\n\n```html\n\u003chtml\u003e\n    \u003ch1\u003e\n        {{ .title }}\n    \u003c/h1\u003e\n\u003c/html\u003e\n```\n\n## Middleware\n\n`looli.Default()` with middleware `Logger()` `Recover()` by default, without middleware use `looli.New()` instead.\n\n### Using middleware\n\n```go\npackage main\n\nimport (\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n    \"log\"\n)\n\nfunc main() {\n    router := looli.New()\n\n    // global middleware\n    router.Use(looli.Logger())\n    router.Get(\"/a\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello world!\\n\")\n    })\n\n    // multi handler for specificed path\n    router.Get(\"/b\", func(c *looli.Context) {\n        c.String(\"first handler\\n\")\n    }, func(c *looli.Context) {\n        c.String(\"second handler\\n\")\n    })\n\n    v1 := router.Prefix(\"/v1\")\n\n    // recover middleware only work for /v1 prefix router\n    v1.Use(looli.Recover())\n    v1.Get(\"/a\", func(c *looli.Context) {\n        panic(\"error!\")\n        c.Status(200)\n        c.String(\"hello world!\\n\")\n    })\n\n    log.Fatal(http.ListenAndServe(\":8080\", router))\n}\n```\n\n### Builtin middlewares\n\n* Logger middleware\n* Recover middleware\n* [Session middleware](https://github.com/cssivision/looli/tree/master/session)\n* [Cors middleware](https://github.com/cssivision/looli/tree/master/cors)\n* [Csrf middleware](https://github.com/cssivision/looli/tree/master/csrf)\n\n### Custome middleware\n\n```go\npackage main\n\nimport (\n    \"log\"\n    \"net/http\"\n    \"github.com/cssivision/looli\"\n    \"time\"\n)\n\nfunc Logger() looli.HandlerFunc {\n    return func(c *looli.Context) {\n        t := time.Now()\n        // before request\n        c.Next()\n        // after request\n        latency := time.Since(t)\n        log.Print(latency)\n    }\n}\n\nfunc main() {\n    router := looli.New()\n\n    // global middleware\n    router.Use(Logger())\n    router.Get(\"/a\", func(c *looli.Context) {\n        c.Status(200)\n        c.String(\"hello world!\\n\")\n    })\n\n    http.ListenAndServe(\":8080\", router)\n}\n```\n\n# Licenses\n\nAll source code is licensed under the [MIT License](https://github.com/cssivision/looli/blob/master/LICENSE).\n\n# Todo\n\n* elegant error handle\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcssivision%2Flooli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcssivision%2Flooli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcssivision%2Flooli/lists"}