{"id":13804494,"url":"https://github.com/mmontone/easy-routes","last_synced_at":"2026-02-05T19:34:26.722Z","repository":{"id":138624290,"uuid":"95926182","full_name":"mmontone/easy-routes","owner":"mmontone","description":"Yet another routes handling utility on top of Hunchentoot","archived":false,"fork":false,"pushed_at":"2025-05-22T12:36:35.000Z","size":69,"stargazers_count":62,"open_issues_count":1,"forks_count":3,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-07-01T14:52:36.830Z","etag":null,"topics":["common-lisp","hunchentoot","lisp","routing","web"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","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/mmontone.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,"zenodo":null}},"created_at":"2017-06-30T21:45:53.000Z","updated_at":"2025-06-16T10:41:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"14d3a63a-5e36-4eb2-9af6-daecf0f7e565","html_url":"https://github.com/mmontone/easy-routes","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mmontone/easy-routes","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Feasy-routes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Feasy-routes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Feasy-routes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Feasy-routes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mmontone","download_url":"https://codeload.github.com/mmontone/easy-routes/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmontone%2Feasy-routes/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29130588,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T18:55:47.139Z","status":"ssl_error","status_checked_at":"2026-02-05T18:55:04.010Z","response_time":65,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["common-lisp","hunchentoot","lisp","routing","web"],"created_at":"2024-08-04T01:00:48.827Z","updated_at":"2026-02-05T19:34:26.705Z","avatar_url":"https://github.com/mmontone.png","language":"Common Lisp","funding_links":[],"categories":["REPLs ##","Interfaces to other package managers"],"sub_categories":["Hunchentoot plugins"],"readme":"# EASY-ROUTES #\n\nEASY-ROUTES is yet another routes handling system on top of Hunchentoot.\n\nIt's just glue code for Restas routing subsystem (CL-ROUTES).\n\nIt supports:\n\n* Dispatch based on HTTP method\n* Arguments extraction from the url path\n* Decorators\n* Url generation from route names\n\n## Usage ##\n\nUse `routes-acceptor` acceptor:\n\n```lisp\n(hunchentoot:start (make-instance 'easy-routes:routes-acceptor))\n```\n\nNote that the `routes-acceptor` returns with HTTP not found if no route matches and doesn't fallback to `easy-handlers`, and so it doesn't iterate over Hunchentoot `*dispatch-table*`. Most of the time, that iteration is a useful thing, so you may want to start the `easy-routes:easy-routes-acceptor` instead, that inherits from Hunchentoot `easy-acceptor` and so it iterates the dispatch table if no route matches (useful for being able to use `define-easy-handler` and also handling static files).\n\n### Breaking changes\n\n- Commit 9a783af3492b8f7d8ed3ac7b2a720fde71fbc7a2 changes the order of arguments for decorators. There's a good reason for this, to be able for decorators to have optional and key arguments. So from this commit on, the `next` function is passed first.\n\n## Routes ##\n\n### Syntax ###\n\n```lisp\n\n(defroute \u003cname\u003e (\u003cpath\u003e \u0026rest \u003croute-options\u003e) \u003croute-params\u003e \n   \u0026body body)\n   \n```\n\nwith:\n\n* `path`: A string with an url path that can contain arguments prefixed with a colon. \n  Like `\"/foo/:x/:y\"`, where `:x` and `:y` are bound into x and y variables in the context of the route body.\n* `route-options`: possible options are\n     * `:method` - The HTTP method to dispatch, as a keyword. Default is `:get`.\n     * `:decorators` - The decorators to attach (see \"decorators\" section below).\n     * `:acceptor-name` - The name of the acceptor the route should be added to (optional).\n* `route-params`: a list of params to be extracted from the url or HTTP request body (POST). \n     Has this form: `(params \u0026get get-params \u0026post post-params \u0026path path-params)`, with the `\u0026get`, `\u0026post` and `\u0026path` params sections being optional, and where `params` are grabbed via `hunchentoot:parameter` function, `get-params` via `hunchentoot:get-parameter` function, and `post-params` via `hunchentoot:post-parameter` function. `path-params` specifies the type of params in the url path (see below for an example).\n        \n    For example:\n\n    ```lisp\n    (easy-routes:defroute name (\"/foo/:x\") (y \u0026get z)\n        (format nil \"x: ~a y: ~a z: ~a\" x y z))\n    ```\n    Also, params can have Hunchentoot easy-handler style options, described here: http://weitz.de/hunchentoot/#define-easy-handler\n    \n    ```\n    (var \u0026key real-name parameter-type init-form request-type)\n    ```\n       \n    For example:\n    \n    ```lisp\n    (easy-routes:defroute foo \"/foo/:x\" \n        ((y :real-name \"Y\" :init-form 22 :parameter-type 'integer))\n      (format nil \"~A - ~A\" x y)) \n    ```\n    \n    You can also specify the type of path parameters after `\u0026path`. For example, say you want to sum a path argument to a query argument. You can specify their type as 'INTEGER and calculate their sum without parsing:\n    \n    ```lisp\n    (easy-routes:defroute foo \"/foo/:x\" \n        ((y :init-form 10 :parameter-type 'integer) \n            \u0026path (x 'integer))\n                  (format nil \"~A\" (+ x y)))\n    ```\n    \n### Example route ###\n\n```lisp\n(defroute foo (\"/foo/:arg1/:arg2\" :method :get\n                                  :decorators (@auth @db @html))\n   (\u0026get w)\n    (format nil \"\u003ch1\u003eFOO arg1: ~a arg2: ~a ~a\u003c/h1\u003e\" arg1 arg2 w))\n```\n## Url generation\n\nUse `genurl` function with the name of the route and route parameters as keyword arguments to generate urls.\n\nExample:\n\n```lisp\n(genurl 'save-payment-form :id (id application))\n```\n\n## Decorators ##\n\nDecorators are functions that are executed before the route body. They should call the `next` parameter function to continue executing the decoration chain and the route body finally.\n\n### Examples ###\n\n```lisp\n(defun @auth (next)\n  (let ((*user* (hunchentoot:session-value 'user)))\n    (if (not *user*)\n\t(hunchentoot:redirect \"/login\")\n\t(funcall next))))\n\n(defun @html (next)\n  (setf (hunchentoot:content-type*) \"text/html\")\n  (funcall next))\n\n(defun @json (next)\n  (setf (hunchentoot:content-type*) \"application/json\")\n  (funcall next))\n\n(defun @db (next)\n  (postmodern:with-connection *db-spec*\n    (funcall next)))\n```\n\nDecorators also support parameters, like in the `@check` and `@check-permission` decorators:\n\n```lisp\n(defun @check (next predicate http-error)\n  (if (funcall predicate)\n      (funcall next)\n      (http-error http-error)))\n\n(defun @check-permission (next predicate)\n  (if (funcall predicate)\n      (funcall next)\n      (permission-denied-error)))\n ```\n \n Then you can use those decorators passing the needed parameters. `predicate` and `http-error` for `@check`, \n and `predicate` for check permission:\n \n ```lisp\n (defroute my-protected-route (\"/foo\" :method :get\n                                      :decorators ((@check my-permissions-checking-function hunchentoot:+http-forbidden+)))\n\t()\t\t\t      \n\t...)\n```\n\n### List of included decorators\n\n`@content-type`, `@html`, `@json`, `@headers-out`, `@header-out`, `@accept`, `@cors`, `@check`, `@check-permission`.\n\n## Routes for individual acceptors\n\nBy default routes are registered globally in `*ROUTES*` and `*ROUTES-MAPPER*` variables.\nThat is convenient for the most common case of running a single EASY-ROUTES service per Lisp image.\n\nBut it gets problematic if you want to run several EASY-ROUTES based services on the same Lisp image. \nIf routes are registered globally, then all your acceptors use the same routes and mapper; \nthat means that a service A would also respond to routes defined in service B; that's clearly not what you want.\n\nFor that case, you can use `acceptor names` to define routes for a specific acceptor.\n\nFirst you need to give your acceptor a name, using `:name` acceptor parameter:\n\n```lisp\n(hunchentoot:start (make-instance 'easy-routes:routes-acceptor :name 'my-service))\n```\n\nThen, use that name in routes definition `:acceptor-name`:\n\n```lisp\n(defroute my-route (\"/my-route\" :acceptor-name my-service)\n  ...\n)\n```\n\nNow `my-route` is registered locally to the `my-service` acceptor; other running EASY-ROUTES acceptors don't have it in their map anymore.\nThat means you can run several EASY-ROUTES acceptors at the same time on the same Lisp image now.\n\nLastly, you need to use `:acceptor-name` when generating urls now too:\n\n```lisp\n(genurl 'my-route :acceptor-name 'my-service)\n```\n## Map of routes visualization\n\nCL-ROUTES package implement special SWANK code for routes map visualization. Just inspect `*ROUTES-MAP*` variable from your lisp listener.\n\nFor example:\n\n```\n#\u003cROUTES:MAPPER {1007630E53}\u003e\n--------------------\n\nTree of routes\n--------------------------------------------------\n\nusers invoice-engine::admin/users\napi/invoices/chart invoice-engine::invoices-chart-data\ninvoice-engine::dashboard\nlogout invoice-engine::logout\ncompany/logo invoice-engine::company-logo\nsearch invoice-engine::global-search\npreview-invoice invoice-engine::preview-invoice\ndt-invoices invoice-engine::datatables-list-invoices\ntenants invoice-engine::admin/tenants\nadmin/\n    settings invoice-engine::admin/settings\n    invoice-engine::admin/dashboard\n    tenants/new/\n        invoice-engine::admin/tenants/create\n        invoice-engine::admin/tenants/new\n    login/\n        invoice-engine::admin/signin\n        invoice-engine::admin/login\n    tenant/$id invoice-engine::admin/tenant\n    users/\n        new/\n            invoice-engine::admin/users/create\n            invoice-engine::admin/users/new\n        $id/edit/\n            invoice-engine::admin/users/update\n            invoice-engine::admin/users/edit\ncustomers/\n    invoice-engine::web/list-customers\n    $id invoice-engine::view-customer\ninvoices/\n    invoice-engine::list-invoices-route\n    $id/\n        print invoice-engine::web/print-invoice\n        printed invoice-engine::web/printed-invoice\n        invoice-engine::view-invoice\n        send invoice-engine::web/send-invoice-by-email\n\n```\n\nLess fancy, but useful too, you can also use `(describe easy-routes:*routes-mapper*)` to visualize the tree of routes.\n\n## Augmented error pages and logs\n\nYou can get augmented error pages and logs with request, session and route information, adding `easy-routes+errors` as dependency, and subclassing from `easy-routes-errors-acceptor`, like: \n\n```lisp\n(defclass my-acceptor (easy-routes:easy-routes-acceptor easy-routes::easy-routes-errors-acceptor)\n  ())\n ```\n This is implemented with [hunchentoots-errors](https://github.com/mmontone/hunchentoot-errors) library.\n\n## Djula integration\n\n`easy-routes+djula` system implements support for generating easy-routes urls using route names and arguments in Djula templates (calls `genurl` function).\n\nDjula template syntax:\n```\n{% genurl route-name \u0026rest args %}\n```\n\nExample:\n```\n{% genurl my-route :id 22 :foo template-var.key %}\n```\n\n## Reference ##\n\n## Functions\n### @html\n\n```lisp\n(next)\n```\n\nHTML decoration. Sets reply content type to text/html\n\n### find-route\n\n```lisp\n(name)\n```\n\nFind a route by name (symbol)\n\n### genurl\n\n```lisp\n(route-symbol \u0026rest args \u0026key \u0026allow-other-keys)\n```\n\nGenerate a relative url from a route name and arguments\n\n### genurl\\*\n\n```lisp\n(route-symbol \u0026rest args \u0026key \u0026allow-other-keys)\n```\n\nGenerate an absolute url from a route name and arguments\n\n### redirect\n\n```lisp\n(route-symbol \u0026rest args)\n```\nRedirect to a route url. Pass the route name and the parameters.\n\n\n## Macros\n### defroute\n\n```lisp\n(name template-and-options params \u0026body body)\n```\n\nRoute definition syntax\n\n## Classes\n\n### easy-routes-acceptor\nThis acceptor tries to match and handle easy-routes first, but fallbacks to easy-routes dispatcher if there's no matching\n\n### routes-acceptor\nThis acceptors handles routes and only routes. If no route is matched then an HTTP NOT FOUND error is returned.\nIf you want to use Hunchentoot easy-handlers dispatch as a fallback, use EASY-ROUTES-ACCEPTOR\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmontone%2Feasy-routes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmmontone%2Feasy-routes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmontone%2Feasy-routes/lists"}