{"id":32216345,"url":"https://github.com/domm/plack-middleware-prettyexception","last_synced_at":"2026-07-13T02:31:46.433Z","repository":{"id":14772581,"uuid":"77065911","full_name":"domm/Plack-Middleware-PrettyException","owner":"domm","description":"Capture exceptions and present them as HTML or JSON","archived":false,"fork":false,"pushed_at":"2022-10-21T12:53:45.000Z","size":33,"stargazers_count":0,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-08-20T22:24:20.971Z","etag":null,"topics":["hacktoberfest","plack","psgi"],"latest_commit_sha":null,"homepage":"","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/domm.png","metadata":{"files":{"readme":"README.md","changelog":"Changes","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-21T16:02:11.000Z","updated_at":"2022-10-21T12:53:48.000Z","dependencies_parsed_at":"2022-08-07T08:00:34.750Z","dependency_job_id":null,"html_url":"https://github.com/domm/Plack-Middleware-PrettyException","commit_stats":null,"previous_names":[],"tags_count":11,"template":null,"template_full_name":null,"purl":"pkg:github/domm/Plack-Middleware-PrettyException","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domm%2FPlack-Middleware-PrettyException","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domm%2FPlack-Middleware-PrettyException/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domm%2FPlack-Middleware-PrettyException/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domm%2FPlack-Middleware-PrettyException/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/domm","download_url":"https://codeload.github.com/domm/Plack-Middleware-PrettyException/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/domm%2FPlack-Middleware-PrettyException/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280402184,"owners_count":26324587,"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","status":"online","status_checked_at":"2025-10-22T02:00:06.515Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["hacktoberfest","plack","psgi"],"created_at":"2025-10-22T07:58:17.277Z","updated_at":"2025-10-22T07:58:18.764Z","avatar_url":"https://github.com/domm.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NAME\n\nPlack::Middleware::PrettyException - Capture exceptions and present them as HTML or JSON\n\n# VERSION\n\nversion 1.010\n\n# SYNOPSIS\n\n    use Plack::Builder;\n    builder {\n        enable \"Plack::Middleware::PrettyException\",\n        $app;\n    };\n\n    # then in your app in some controller / model / wherever\n\n    # just die\n    die \"something went wrong\";\n\n    # use HTTP::Throwable\n    http_throw(\n        NotAcceptable =\u003e { message =\u003e 'You have to be kidding me!' }\n    );\n\n    # use a custom exception that implements http_status\n    My::X::BadParam-\u003ethrow({ status=\u003e400, message=\u003e'required param missing'})\n\n    # clients get either\n    #   JSON (if Accept-header indicates that JSON is expected)\n    #   or a plain HTML error message\n\n# DESCRIPTION\n\n`Plack::Middleware::PrettyException` allows you to use exceptions in\nyour models (and also controllers, but they should be kept slim!) and\nhave them rendered as JSON or nice-ish HTML with very little fuzz.\n\nBut if your Plack app returns an HTTP status code indicating an error\n(4xx/5xx) or just dies somewhere, the client also sees a pretty\nexception.\n\nSo instead of capturing exceptions in your controller actions and\nconverting them to proper error messages, you just let the exception\npropagate up to this middleware, which will then do the rendering.\nThis leads to much cleaner code in your controller, and to proper\nexception usage in your model.\n\n## Example\n\nHere is am example controller implementing some kind of update:\n\n    # SomeController.pm\n    sub some_update_action {\n        my ($self, $req, $id) = @_;\n\n        my $item = $self-\u003esome_model-\u003eload_item($id);\n        my $user = $req-\u003eget_user;\n        $self-\u003eauth_model-\u003emay_edit($user, $item);\n\n        my $payload = $req-\u003eget_json_payload;\n\n        my $rv = $self-\u003esome_model-\u003eupdate_item($item, $payload);\n\n        $req-\u003ejson_response($rv);\n    }\n\nThe lack of error handling makes the intention of this piece of code very clear. \"The code is easy to reason about\", as the current saying goes.\n\nHere's the matching model\n\n    # SomeModel\n    sub load_item {\n        my ($self, $id) = @_;\n\n        my $item = $self-\u003eresultset('Foo')-\u003efind($id);\n        return $item if $item;\n\n        My::X::NotFound-\u003ethrow({\n            ident=\u003e'cannot_find_foo',\n            message=\u003e'Cannot load Foo from id %{id}s',\n            id=\u003e$id,\n        });\n    }\n\n`My::X::NotFound` could be a exception class based on [Throwable::X](https://metacpan.org/pod/Throwable::X):\n\n    package My::X;\n    use Moose;\n    with qw(Throwable::X);\n    \n    use Throwable::X -all;\n    \n    has [qw(http_status)] =\u003e (\n        is      =\u003e 'ro',\n        default =\u003e 400,\n        traits  =\u003e [Payload],\n    );\n    \n    no Moose;\n    __PACKAGE__-\u003emeta-\u003emake_immutable;\n    \n    package My::X::NotFound;\n    use Moose;\n    extends 'My::X';\n    use Throwable::X -all;\n    \n    has id =\u003e (\n        is     =\u003e 'ro',\n        traits =\u003e [Payload],\n    );\n    \n    has '+http_status' =\u003e ( default =\u003e 404, );\n\nIf we now call the endpoint with an invalid id, we get:\n\n    ~$ curl -i http://localhost/thing/42/update\n    HTTP/1.1 404 Not Found\n    Content-Type: text/html;charset=utf-8\n    \n    \u003chtml\u003e\n      \u003chead\u003e\u003ctitle\u003eError 404\u003c/title\u003e\u003c/head\u003e\n      \u003cbody\u003e\n        \u003ch1\u003eError 404\u003c/h1\u003e\n        \u003cp\u003eCannot load Foo from id 42\u003c/p\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n\nIf we want JSON, we just need to tell the server:\n\n    ~$ curl -i -H 'Accept: application/json' http://localhost/thing/42/update\n\n    HTTP/1.1 404 Not Found\n    Content-Type: application/json\n\n    {\"status\":\"error\",\"message\":\"Cannot load Foo from id 42\"}\n\nSmooth!\n\n## Content Negotiation / Force JSON\n\nAs of now there is no real content-negotiation, because all I need is\nHTML and JSON. There is some semi-dumb checking of the\n`Accept`-Header, but I only check for literal `application/json`\n(while I should do the whole q-factor weighting dance).\n\nIf you want to force all your errors to JSON, pass `force_json =\u003e 1`\nwhen loading the middleware:\n\n    builder {\n        enable \"Plack::Middleware::PrettyException\" =\u003e ( force_json =\u003e 1 );\n        $app\n    };\n\nThis will be replace in the near future by some proper content\nnegitiation and a new `default_response_encoding` field.\n\n## Finetune HTML output via subclassing\n\nThe default HTML is rather basic^wugly. To finetune this, just\nsubclass `Plack::Middleware::PrettyException` and implement a method\ncalled `render_html_error`. This method will be called with the HTTP\nstatus code, the stringified error message, the original exception\nobject (if there was one!) and the original request `$env`. You can\nthen render it as fancy as you (or your graphic designer) wants.\n\nHere's an example:\n\n    package Oel::Middleware::Error;\n    \n    use 5.020;\n    use strict;\n    use warnings;\n    use parent qw(Plack::Middleware::PrettyException);\n    use Plack::Util::Accessor qw(html_page_model renderer);\n    \n    sub render_html_error {\n        my ( $self, $status, $message, $exception, $env ) = @_;\n    \n        my %data = (base=\u003e'/',title=\u003e'Error '.$status, error =\u003e $message, code =\u003e $status );\n        eval {\n            if (my $page = $self-\u003ehtml_page_model-\u003eload('/_error/'.$status)) {\n              $data{title} = $page-\u003etitle;\n              $data{description} = $page-\u003eteaser;\n            }\n        };\n    \n        my $rendered='';\n        $self-\u003erenderer-\u003ett-\u003eprocess('error.tt',\\%data,\\$rendered);\n        return $rendered if $rendered;\n    \n        return \"Error while rendering error: \".$self-\u003erenderer-\u003ett-\u003eerror;\n    }\n    \n    1;\n\nThis middleware uses a `html_page_model` to retrieve the title and\ndescription of the error page from a database (where admins can edit\nthose fields via a CMS). It uses\n[Template::Toolkit](https://metacpan.org/pod/Template) to render the\npage.\n\n`html_page_model` and `renderer` are two attributes needed by this\nmiddleware, implemented via `Plack::Util::Accessor`. You have to\nprovide some meaningful objects when loading the middleware, maybe\nlike this:\n\n    use Plack::Builder;\n\n    builder {\n        enable \"Plack::Middleware::PrettyException\",\n            renderer        =\u003e Oel::Renderer-\u003enew,\n            html_page_model =\u003e Oel::Model::HtmlPage-\u003enew;\n\n        $app;\n    };\n\nOf course you'll need to init `Oel::Renderer` and\n`Oel::Model::HtmlPage`, so you'll probably want to use\n[Bread::Board](https://metacpan.org/pod/Bread::Board) or\n[OX](https://metacpan.org/pod/OX).\n\n# SEE ALSO\n\n- [Plack::Middleware::ErrorDocument](https://metacpan.org/pod/Plack::Middleware::ErrorDocument)\n\n    Set Error Document based on HTTP status code. Does not capture errors.\n\n- [Plack::Middleware::CustomErrorDocument](https://metacpan.org/pod/Plack::Middleware::CustomErrorDocument)\n\n    Dynamically select error documents based on HTTP status code. Improved version of Plack::Middleware::ErrorDocument, also does not capture errors.\n\n- [Plack::Middleware::DiePretty](https://metacpan.org/pod/Plack::Middleware::DiePretty)\n\n    Show a 500 error page if you die. Converts **all** exceptions to 500 Server Error.\n\n# THANKS\n\nThanks to\n\n- [validad.com](https://www.validad.com/) for supporting Open Source.\n- [oe1.orf.at](http://oe1.orf.at) for the motivation to extract the code from the Validad stack.\n- [sixtease](https://metacpan.org/author/SIXTEASE) for coming up with `Oel` as the name for my example app (after miss-reading `oe1`).\n\n# AUTHOR\n\nThomas Klausner \u003cdomm@plix.at\u003e\n\n# COPYRIGHT AND LICENSE\n\nThis software is copyright (c) 2016 - 2022 by Thomas Klausner.\n\nThis is free software; you can redistribute it and/or modify it under\nthe same terms as the Perl 5 programming language system itself.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomm%2Fplack-middleware-prettyexception","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdomm%2Fplack-middleware-prettyexception","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdomm%2Fplack-middleware-prettyexception/lists"}