{"id":13776285,"url":"https://github.com/vm-001/lua-radix-router","last_synced_at":"2025-04-10T02:18:58.505Z","repository":{"id":214015766,"uuid":"730067396","full_name":"vm-001/lua-radix-router","owner":"vm-001","description":"A lightweight high-performance and radix tree based router for Lua/LuaJIT/OpenResty","archived":false,"fork":false,"pushed_at":"2025-03-31T07:49:36.000Z","size":139,"stargazers_count":205,"open_issues_count":3,"forks_count":11,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-01T16:20:03.966Z","etag":null,"topics":["fast","kong","lua","luajit","openapi","openresty","restful","router","routing","swagger","trie"],"latest_commit_sha":null,"homepage":"https://vm-001.github.io/lua-radix-router/","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vm-001.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,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-11T06:10:24.000Z","updated_at":"2025-03-31T07:52:59.000Z","dependencies_parsed_at":"2024-04-09T17:44:40.010Z","dependency_job_id":"c444fde7-9975-45be-8920-1bd5ae51298c","html_url":"https://github.com/vm-001/lua-radix-router","commit_stats":{"total_commits":37,"total_committers":2,"mean_commits":18.5,"dds":"0.027027027027026973","last_synced_commit":"7dd5ae3641be338a5439ca1742bb545fcf7715bb"},"previous_names":["vm-001/lua-radix-router"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vm-001%2Flua-radix-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vm-001%2Flua-radix-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vm-001%2Flua-radix-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vm-001%2Flua-radix-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vm-001","download_url":"https://codeload.github.com/vm-001/lua-radix-router/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248142906,"owners_count":21054672,"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":["fast","kong","lua","luajit","openapi","openresty","restful","router","routing","swagger","trie"],"created_at":"2024-08-03T18:00:21.948Z","updated_at":"2025-04-10T02:18:58.473Z","avatar_url":"https://github.com/vm-001.png","language":"Lua","funding_links":[],"categories":["Libraries","Lua"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg alt=\"special sponsor appwrite\" src=\"./lua-radix-router.png\" width=\"600\"\u003e\n\u003c/p\u003e\n\n# Lua-Radix-Router [![Build Status](https://github.com/vm-001/lua-radix-router/actions/workflows/test.yml/badge.svg)](https://github.com/vm-001/lua-radix-router/actions/workflows/test.yml) [![Build Status](https://github.com/vm-001/lua-radix-router/actions/workflows/examples.yml/badge.svg)](https://github.com/vm-001/lua-radix-router/actions/workflows/examples.yml) [![Coverage Status](https://coveralls.io/repos/github/vm-001/lua-radix-router/badge.svg)](https://coveralls.io/github/vm-001/lua-radix-router) ![Lua Versions](https://img.shields.io/badge/Lua-%205.2%20|%205.3%20|%205.4-blue.svg)\n\nEnglish | [中文](README.zh.md)\n\n\n\nLua-Radix-Router is a lightweight high-performance router library written in pure Lua. It's easy to use with only two exported functions, `Router.new()` and `router:match()`.\n\nThe router is optimized for high performance. It combines HashTable(O(1)) and Compressed Trie(or Radix Tree, O(m) where m is the length of path being searched) for efficient matching. Some of the utility functions have the LuaJIT version for better performance, and will automatically switch when running in LuaJIT. It also scales well even with long paths and a large number of routes.\n\nThe router can be run in different runtimes such as Lua, LuaJIT, or OpenResty.\n\nThis library is considered production ready.\n\n## 🔨 Features\n\n**Patterned path:** You can define named or unnamed patterns in path with pattern syntax \"{}\" and \"{*}\"\n\n-   named variables: `/users/{id}/profile-{year}.{format}`, matches with /users/1/profile-2024.html.\n-   named prefix: `/api/authn/{*path}`, matches with /api/authn/foo and /api/authn/foo/bar.\n\n**Variable binding:** Stop manually parsing the URL, let the router injects the binding variables for you.\n\n**Best performance:** The fastest router in Lua/LuaJIT and open-source API Gateways. See [Benchmarks](#-Benchmarks) and [Routing Benchmark](https://github.com/vm-001/gateways-routing-benchmark) in different API Gateways.\n\n**OpenAPI friendly:** OpenAPI(Swagger) is fully compatible.\n\n**Trailing slash match:** You can make the Router to ignore the trailing slash by setting `trailing_slash_match` to true. For example, /foo/ to match the existing /foo, /foo to match the existing /foo/.\n\n**Custom Matcher:** The router has two efficient matchers built in, MethodMatcher(`method`) and HostMatcher(`host`). They can be disabled via `opts.matcher_names`. You can also add your custom matchers via `opts.matchers`. For example, an IpMatcher to evaluate whether the `ctx.ip` is matched with the `ips` of a route.\n\n**Regex pattern:** You can define regex pattern in variables. a variable without regex pattern is treated as `[^/]+`.\n\n- `/users/{uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}`\n- `/users/{id:\\\\d+}/profile-{year:\\\\d{4}}.{format:(html|pdf)}`\n\n**Features in the roadmap**:\n\n- Expression condition: defines custom matching conditions by using expression language.\n\n## 📖 Getting started\n\nInstall radix-router via LuaRocks:\n\n```\nluarocks install radix-router\n```\n\nOr from source\n\n```\nmake install\n```\n\nGet started by an example:\n\n```lua\nlocal Router = require \"radix-router\"\nlocal router, err = Router.new({\n  { -- static path\n    paths = { \"/foo\", \"/foo/bar\", \"/html/index.html\" },\n    handler = \"1\" -- handler can be any non-nil value. (e.g. boolean, table, function)\n  },\n  { -- variable path\n    paths = { \"/users/{id}/profile-{year}.{format}\" },\n    handler = \"2\"\n  },\n  { -- prefix path\n    paths = { \"/api/authn/{*path}\" },\n    handler = \"3\"\n  },\n  { -- methods condition\n    paths = { \"/users/{id}\" },\n    methods = { \"POST\" },\n    handler = \"4\"\n  }\n})\nif not router then\n  error(\"failed to create router: \" .. err)\nend\n\nassert(\"1\" == router:match(\"/html/index.html\"))\nassert(\"2\" == router:match(\"/users/100/profile-2023.pdf\"))\nassert(\"3\" == router:match(\"/api/authn/token/genreate\"))\nassert(\"4\" == router:match(\"/users/100\", { method = \"POST\" }))\n\n-- variable binding\nlocal params = {}\nrouter:match(\"/users/100/profile-2023.pdf\", nil, params)\nassert(params.year == \"2023\")\nassert(params.format == \"pdf\")\n```\n\nFor more usage samples, please refer to the [/examples](/examples) directory. For more use cases, please check out [lua-radix-router-use-cases](https://github.com/vm-001/lua-radix-router-use-cases).\n\n## 📄 Methods\n\n### new\n\nCreates a radix router instance.\n\n```lua\nlocal router, err = Router.new(routes, opts)\n```\n\n**Parameters**\n\n- **routes** (`table|nil`): the array-like Route table.\n\n- **opts** (`table|nil`): the object-like Options table.\n\n    The available options are as follow\n\n    | NAME                 | TYPE    | DEFAULT           | DESCRIPTION                                         |\n    | -------------------- | ------- | ----------------- | --------------------------------------------------- |\n    | trailing_slash_match | boolean | false             | whether to enable the trailing slash match behavior |\n    | matcher_names        | table   | {\"method\",\"host\"} | enabled built-in macher list                        |\n    | matchers             | table   | { }               | custom matcher list                                 |\n\n\n\nRoute defines the matching conditions for its handler.\n\n| PROPERTY                      | DESCRIPTION                                                                                                                                                                              |\n|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `paths`\u003c/br\u003e *required\\**    | A list of paths that match the Route.\u003c/br\u003e                                                                                                                                               |\n| `methods`\u003c/br\u003e *optional*     | A list of HTTP methods that match the Route. \u003c/br\u003e                                                                                                                                       |\n| `hosts`\u003c/br\u003e *optional*            | A list of hostnames that match the Route. Note that the value is case-sensitive. Wildcard hostnames are supported. For example, `*.foo.com` can match with `a.foo.com` or `a.b.foo.com`. |\n| `handler`\u003c/br\u003e *required\\**        | The value of handler will be returned by `router:match()` when the route is matched.                                                                                                     |\n| `priority`\u003c/br\u003e *optional*         | The priority of the route in case of radix tree node conflict.                                                                                                                           |\n\n\n\n### match\n\nReturn the handler of a matched route that matches the path and condition ctx.\n\n```lua\nlocal handler = router:match(path, ctx, params, matched)\n```\n\n**Parameters**\n\n- **path**(`string`): the path to use for matching.\n- **ctx**(`table|nil`): the optional condition ctx to use for matching.\n- **params**(`table|nil`): the optional table to use for storing the parameters binding result.\n- **matched**(`table|nil`): the optional table to use for storing the matched conditions.\n\n## 📝 Examples\n\n#### Regex pattern\n\nUsing regex to define the pattern of a variable. Note that at most one URL segment is evaluated when matching a variable's pattern, which means it's not allowed to define a pattern crossing multiple URL segments, for example, `{var:[/0-9a-z]+}`.\n\n```lua\nlocal Router = require \"radix-router\"\nlocal router = Router.new({\n  {\n    paths = { \"/users/{id:\\\\d+}/profile-{year:\\\\d{4}}.{format:(html|pdf)}\" },\n    handler = \"1\"\n  },\n  {\n    paths = { \"/users/{uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}\" },\n    handler = \"2\"\n  },\n})\nassert(\"1\" == router:match(\"/users/100/profile-2024.pdf\"))\nassert(\"2\" == router:match(\"/users/00000000-0000-0000-0000-000000000000\"))\n```\n\n## 🧠 Data Structure and Implementation\n\nInside the Router, it has a hash-like table to optimize the static path matching. Due to the LuaJIT optimization, static path matching is the fastest and has lower memory usage. (see [Benchmarks](#-Benchmarks))\n\nThe Router also has a tree structure for patterned path matching. The tree is basically a compact [prefix tree](https://en.wikipedia.org/wiki/Trie) (or [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree)). The primary structure of Router is as follows:\n\n```\n{\n  static\u003cTable\u003e   = {},\n  trie\u003cTrieNode\u003e  = TrieNode.new(),\n  ...\n}\n\n+--------+----------+------------------------------------+\n| FIELD  |   TYPE   |                DESC                |\n+--------+----------+------------------------------------+\n| static | table    | a hash-like table for static paths |\n| trie   | TrieNode | a radix tree for pattern paths     |\n+--------+----------+------------------------------------+\n```\n\nTrieNode is an array-like table. Compared with the hash-like, it reduces memory usage by 20%. The data structure of TrieNode is:\n\n```\n{ \u003ctype\u003e, \u003cpath\u003e, \u003cpathn\u003e, \u003cchildren\u003e, \u003cvalue\u003e }\n\n+-------+----------+------------------+\n| INDEX |   NAME   |       TYPE       |\n+-------+----------+------------------+\n|     1 | type     | integer          |\n|     2 | path     | string           |\n|     3 | pathn    | integer          |\n|     4 | children | hash-like table  |\n|     5 | value    | array-like table |\n+-------+----------+------------------+\n```\n\nNodes with a common prefix share a common parent. Here is an example of what a Router with three routes could look like:\n\n```lua\nlocal router = Router.new({\n  { -- \u003ctable 1\u003e\n    paths = { \"/api/login\" },\n    handler = \"1\",\n  }, { -- \u003ctable 2\u003e\n    paths = { \"/people/{id}/profile\" },\n    handler = \"2\",\n  }, { -- \u003ctable 3\u003e\n    paths = { \"/search/{query}\", \"/src/{*filename}\" },\n    handler = \"3\"\n  }\n})\n```\n\n\n\n\n```\nrouter.static = {\n  [/api/login] = { *\u003ctable 1\u003e }\n}\n\n              TrieNode.path       TrieNode.value\nrouter.trie = /                   nil\n              ├─people/           nil\n              │ └─{wildcard}      nil\n              │   └─/profile      { \"/people/{id}/profile\",  *\u003ctable 2\u003e }\n              └─s                 nil\n               ├─earch/           nil\n               │ └─{wildcard}     { \"/search/{query}\",       *\u003ctable 3\u003e }\n               └─rc/              nil\n                 └─{catchall}     { \"/src/{*filename}\",      *\u003ctable 3\u003e }\n```\n\n## 🔍 Troubleshooting\n\n\n#### Could not find header file for PCRE2\n\n```\nInstalling https://luarocks.org/lrexlib-pcre2-2.9.2-1.src.rock\n\nError: Failed installing dependency: https://luarocks.org/lrexlib-pcre2-2.9.2-1.src.rock - Could not find header file for PCRE2\n```\n\nTry manually install `lrexlib-pcre2` (on macOS).\n\n```\n$ brew install pcre2\n$ ls /opt/homebrew/opt/pcre2/\n$ luarocks install lrexlib-pcre2 PCRE2_DIR=/opt/homebrew/opt/pcre2\n```\n\n\n## 🚀 Benchmarks\n\n#### Usage\n\nTo run the benchmark\n\n```$ make bench\n$ make install\n$ make bench\n```\n\n#### Environments\n\n- MacBook Pro(M4 Pro), 24GB\n- LuaJIT 2.1.1731601260\n\n#### Results\n\n| test case               | route number | ns / op | OPS         | RSS         |\n|-------------------------|------------:|--------:|------------:|-----------:|\n| static path             | 100000       | 8.70    | 114,984,822 | 48.88 MB    |\n| simple variable         | 100000       | 64.60   | 15,480,954  | 102.55 MB   |\n| simple variable         | 1000000      | 54.78   | 18,257,670  | 976.17 MB   |\n| simple prefix           | 100000       | 50.24   | 19,905,845  | 100.38 MB   |\n| simple regex            | 100000       | 96.72   | 10,338,909  | 111.66 MB   |\n| complex variable        | 100000       | 622.11  | 1,607,443   | 155.05 MB   |\n| simple variable binding | 100000       | 123.16  | 8,119,565   | 100.36 MB   |\n| github                  | 609          | 270.58  | 3,695,778   | 2.88 MB     |\n\n\u003cdetails\u003e\n\u003csummary\u003eExpand output\u003c/summary\u003e\n\n```\nRADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=10000000 luajit benchmark/static-paths.lua\n========== static path ==========\nroutes  :       100000\ntimes   :       10000000\nelapsed :       0.086968 s\nns/op   :       8.6968 ns\nOPS     :       114984822\npath    :       /50000\nhandler :       50000\nMemory  :       48.88 MB\n\nRADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=10000000 luajit benchmark/simple-variable.lua\n========== variable ==========\nroutes  :       100000\ntimes   :       10000000\nelapsed :       0.645955 s\nns/op   :       64.5955 ns\nOPS     :       15480954\npath    :       /1/foo\nhandler :       1\nMemory  :       102.55 MB\n\nRADIX_ROUTER_ROUTES=1000000 RADIX_ROUTER_TIMES=10000000 luajit benchmark/simple-variable.lua\n========== variable ==========\nroutes  :       1000000\ntimes   :       10000000\nelapsed :       0.547715 s\nns/op   :       54.7715 ns\nOPS     :       18257670\npath    :       /1/foo\nhandler :       1\nMemory  :       976.17 MB\n\nRADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=10000000 luajit benchmark/simple-prefix.lua\n========== prefix ==========\nroutes  :       100000\ntimes   :       10000000\nelapsed :       0.502365 s\nns/op   :       50.2365 ns\nOPS     :       19905845\npath    :       /1/a\nhandler :       1\nMemory  :       100.38 MB\n\nRADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=1000000 luajit benchmark/simple-regex.lua\n========== regex ==========\nroutes  :       100000\ntimes   :       1000000\nelapsed :       0.096722 s\nns/op   :       96.722 ns\nOPS     :       10338909\npath    :       /1/a\nhandler :       1\nMemory  :       111.66 MB\n\nRADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=1000000 luajit benchmark/complex-variable.lua\n========== variable ==========\nroutes  :       100000\ntimes   :       1000000\nelapsed :       0.622106 s\nns/op   :       622.106 ns\nOPS     :       1607443\npath    :       /aa/bb/cc/dd/ee/ff/gg/hh/ii/jj/kk/ll/mm/nn/oo/pp/qq/rr/ss/tt/uu/vv/ww/xx/yy/zz50000\nhandler :       50000\nMemory  :       155.05 MB\n\nRADIX_ROUTER_ROUTES=100000 RADIX_ROUTER_TIMES=10000000 luajit benchmark/simple-variable-binding.lua\n========== variable ==========\nroutes  :       100000\ntimes   :       10000000\nelapsed :       1.231593 s\nns/op   :       123.1593 ns\nOPS     :       8119565\npath    :       /1/foo\nhandler :       1\nparams : name = foo\nMemory  :       100.36 MB\n\nRADIX_ROUTER_TIMES=1000000 luajit benchmark/github-routes.lua\n========== github apis ==========\nroutes  :       609\ntimes   :       1000000\nelapsed :       0.270579 s\nns/op   :       270.579 ns\nOPS     :       3695778\npath    :       /repos/vm-001/lua-radix-router/import\nhandler :       /repos/{owner}/{repo}/import\nMemory  :       2.88 MB\n\n```\n\n\u003c/details\u003e\n\n\n## License\n\nBSD 2-Clause License\n\nCopyright (c) 2024, Yusheng Li\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvm-001%2Flua-radix-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvm-001%2Flua-radix-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvm-001%2Flua-radix-router/lists"}