{"id":13616675,"url":"https://github.com/byronka/minum","last_synced_at":"2026-03-05T02:02:42.416Z","repository":{"id":60222319,"uuid":"440010467","full_name":"byronka/minum","owner":"byronka","description":"The smallest fully-tested TDD-designed all-essentials-included non-magic zero-dependency minimalist Java web application framework","archived":false,"fork":false,"pushed_at":"2026-02-25T17:55:55.000Z","size":6932,"stargazers_count":665,"open_issues_count":1,"forks_count":47,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-02-25T20:48:46.176Z","etag":null,"topics":["framework","java","minimal","minimalism","minimalist","minimalistic","tdd","web"],"latest_commit_sha":null,"homepage":"","language":"Java","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/byronka.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-12-20T01:35:16.000Z","updated_at":"2026-02-18T21:19:05.000Z","dependencies_parsed_at":"2025-12-19T15:05:29.433Z","dependency_job_id":null,"html_url":"https://github.com/byronka/minum","commit_stats":null,"previous_names":["byronka/minum","byronka/atqa"],"tags_count":46,"template":false,"template_full_name":null,"purl":"pkg:github/byronka/minum","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byronka%2Fminum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byronka%2Fminum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byronka%2Fminum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byronka%2Fminum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/byronka","download_url":"https://codeload.github.com/byronka/minum/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/byronka%2Fminum/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30106143,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T01:39:18.192Z","status":"online","status_checked_at":"2026-03-05T02:00:06.710Z","response_time":93,"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":["framework","java","minimal","minimalism","minimalist","minimalistic","tdd","web"],"created_at":"2024-08-01T20:01:31.786Z","updated_at":"2026-03-05T02:02:42.405Z","avatar_url":"https://github.com/byronka.png","language":"Java","funding_links":[],"categories":["Web framework for Java","开发框架","framework","Java"],"sub_categories":[],"readme":"Minum Web Framework\n===================\n\nThis codebase provides the facilities necessary to build a web\napplication, with a design that prioritizes server-side-rendering.\nBasic capabilities that any web application would need, like\npersistent storage or templating, are provided.  See _Features_\nfurther down in this document.\n\nYou might find this project well-suited to your needs if your priority\nis maintainability and quality, and if you are aiming to build\nprograms that last years with minimal upkeep.\n\nThere are several examples of projects built with this framework in\nthe _example projects_ below.\n\nHere is a small Minum program (see more _code samples_ below):\n\n```Java\npublic class Main {\n    public static void main(String[] args) {\n        var minum = FullSystem.initialize();\n        var wf = minum.getWebFramework();\n        wf.registerPath(GET, \"\",\n                r -\u003e Response.htmlOk(\"\u003cp\u003eHi there world!\u003c/p\u003e\"));\n        minum.block();\n    }\n}\n```\n\nThe high level characteristics\n-------------------------------\n\n| Capability     | Rating       |\n|----------------|--------------|\n| Small          | `[#######-]` |\n| Tested         | `[#######-]` |\n| Documented     | `[######--]` |\n| Performant     | `[######--]` |\n| Maintainable   | `[#######-]` |\n| Understandable | `[#######-]` |\n| Simple         | `[######--]` |\n| Capable        | `[######--]` |\n\n* Embraces the concept of _kaizen_: small beneficial changes over time\n  leading to impressive capabilities\n* Has its own web server, endpoint routing, logging, templating\n  engine, html parser, assertions framework, and database\n* Designed with TDD (Test-Driven Development)\n* It has 100% test coverage (branch and statement) that runs in 30\n  seconds without any special setup (`make test_coverage`)\n* Has close to 100% mutation test strength using the _PiTest_ mutation\n  testing tool (`make mutation_test`)\n* Relies on no dependencies other than the Java 21 (and up) SDK - i.e.\n  no Netty, Jetty, Tomcat, Log4j, Hibernate, MySql, etc.\n* Is thoroughly documented throughout, anticipating to benefit\n  developers' comprehension\n* No reflection - The framework's method calls are all\n  scope-respecting, and navigation of the code base is plain\n* No annotations - unlike certain other frameworks, the design\n  principle is to solely use ordinary Java method calls\n  for all behavior, and configuration is minimal and relegated to a\n  properties file\n* No magic - nothing unusual behind the scenes, no byte-code runtime\n  surprises.  Just ordinary Java\n* Has examples of framework use - see _Example Projects_ below\n\nMinum has zero dependencies, and is built of ordinary and well-tested\ncode: hashmaps, sockets, and so on. The \"minimalist\" competitors range\nfrom 400,000 to 700,000 lines when accounting for their dependencies.\n\nApplying a minimalist approach enables easier debugging,\nmaintainability, and lower overall cost. Most frameworks trade faster\nstart-up for a higher overall cost. If you need sustainable quality,\nthe software must be well-tested and documented from the onset.  As an\nexample, this project's ability to attain such high test coverage was\ngreatly enabled by the minimalism paradigm. The plentiful tests and\ncomments help to make intent and implementation clearer.\n\nMinum follows _semantic versioning_.\n\nThe design process\n------------------\n\n1) Make it work.\n2) Make it right.\n3) Make it fast.\n\nThis project represents thousands of hours of an experienced\npractitioner experimenting with maintainability, performance, and\npragmatic simplicity.\n\n\nGetting Started\n---------------\n\nThere is a 🚀 [Quick start](docs/quick_start.md), or if you have\na bit more time, consider trying the [tutorial](docs/getting_started/getting_started.md)\n\n\nMaven\n-----\n\n[Maven central repository](https://central.sonatype.com/artifact/com.renomad/minum)\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.renomad\u003c/groupId\u003e\n    \u003cartifactId\u003eminum\u003c/artifactId\u003e\n    \u003cversion\u003e9.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n\nFeatures:\n--------\n\n\u003cdetails\u003e\u003csummary\u003eSecure TLS 1.3 HTTP/1.1 web server\u003c/summary\u003e\n\u003cp\u003e\nA web server is the program that enables sending your data over the internet.\n\u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eIn-memory database with disk persistence\u003c/summary\u003e\n\u003cp\u003e\nA database is necessary to store data for later use, such as user accounts.  Our database stores\nall its data in memory, but writes it to the disk as well.  The only time data is read from disk\nis at database startup.  There are benefits and risks to this approach.\n\u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eServer-side templating\u003c/summary\u003e\n\u003cp\u003e\nA template is just a string with areas you can replace, and the template processor \nrenders these quickly.  For example, here is a template: \"Hello {{ name }}\".  If we\nprovide the template processor with that template and say that \"name\" should be replaced\nwith \"world\", it will do so.\n\u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eLogging\u003c/summary\u003e\n\u003cp\u003e\nLogs are text that the program outputs while running.  There can be thousands of lines\noutput while the program runs.\n\u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eTesting utilities\u003c/summary\u003e\n\u003cp\u003e\nThe test utilities are mostly ways to confirm expectations, and throw an error if unmet.\n\u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eHTML parsing\u003c/summary\u003e\n\u003cp\u003e\nParsing means to interpret the syntax and convert it to meaningful data for later analysis.\nBecause web applications often have to deal with HTML, it is a valuable feature\nin a minimalist framework like this one.\n\u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eBackground queue processor\u003c/summary\u003e\n\u003cp\u003e\nAcross the majority of the codebase, the only time code runs is when a request comes in.  The\nbackground queue processor, however, can continue running programs in parallel.\n\u003c/p\u003e\n\u003c/details\u003e\n\n\nSize Comparison:\n----------------\n\nCompiled size: 209 kilobytes.\n\n_Lines of production code (including required dependencies)_\n\n| Minum | Javalin | Spring Boot |\n|-------|---------|-------------|\n| 6,486 | 141,048 | 1,085,405   |\n\nSee [a size comparison in finer detail](docs/size_comparisons.md)\n\n\nPerformance:\n------------\n\nPerformance is a feature. On your own applications, collect\nperformance metrics at milestones, so that trends and missteps are\nmade apparent.\n\nOne of the benefits of minimalism combined with test-driven\ndevelopment is that finding the bottlenecks and making changes is\neasier, faster, and safer.\n\n* Web request handling: Plenty fast, depending on network and server\n  configuration.  [details here](docs/perf_data/response_speed_test.md)\n* Database updates/reads: 2,000,000 per second. See \"test_Performance\"\n  in DbTests.java. O(1) reads (does not increase in time as database\n  size increases) by use of indexing feature.\n* Template processing:\n  * 100,000 per second for large complex templates. \n\nSee a [Minum versus Spring performance comparison](docs/perf_data/framework_perf_comparison.md)\n\n\nDocumentation:\n--------------\n\n* [Development handbook](docs/development_handbook.md)\n* [Javadocs](https://renomad.com/javadoc/)\n* [Code coverage](https://renomad.com/site/jacoco/index.html) \n* [Mutation test report](https://renomad.com/pit-reports)\n* [Test run report](https://renomad.com/site/surefire-report.html)\n* [Project site report](https://renomad.com/site/project-info.html)\n\n\nExample projects demonstrating usage:\n-------------------------------------\n\nSee the following links for sample projects that use this framework.\n\n[Smallest-possible](https://github.com/byronka/minum_usage_example_smaller)\n\nThis project is valuable to see the minimal-possible application that\ncan be made.  This might be a good starting point for use of Minum on\na new project.\n\n\u003chr\u003e\n\n[Example](https://github.com/byronka/minum_usage_example_mvn) \n\nThis is a good example to see a basic project with various\nfunctionality. It shows many of the typical use cases of the Minum\nframework.\n\n\u003chr\u003e\n\n[Memoria project](https://github.com/byronka/memoria_project)\n\nThis is a family-tree project.  It demonstrates the kind of approach\nthis framework is meant to foster.\n\n\u003chr\u003e\n\n[Restaurants](https://github.com/byronka/restaurants)\n\nRestaurants is a prototype project to provide a customizable ranked\nlist of restaurants.\n\n\u003chr\u003e\n\n[Alternate database](https://github.com/byronka/minum_using_alternate_database)\n\nThis is a project which uses a SQL database called\n[H2](https://www.h2database.com/html/main.html), and which shows how a\nuser might go about including a different database than the one\nbuilt-in.\n\n\nCode samples\n============\n\nThe following code samples help provide an introduction to the features.\n\n* [Database](#database)\n* [Logging](#logging)\n* [HTML Parser](#html-parser)\n* [Endpoints](#endpoints)\n* [Templates](#templates)\n* [User Input](#user-input)\n* [Testing](#testing)\n* [Output](#output)\n* [Helpful utilities](#helpful-utilities)\n\n\nDatabase\n--------\n\n_Instantiating a new database_:\n\nThere are two database options - the older simpler database, or the\nnew DbEngine2 database.  They have nearly identical interfaces and\nexternal behaviors, but the DbEngine2 database reads and writes to\ndisk _magnitudes_ faster than its sibling Db. It is the recommended\ndatabase to use going forward.\n\nHere is how you instantiate the faster database:\n\n```java\nAbstractDb\u003cFoo\u003e db = new DbEngine2\u003c\u003e(foosDirectory, context, new Foo());\n```\n\nHere is the simpler database:\n\n```java\nAbstractDb\u003cFoo\u003e db = new Db\u003c\u003e(foosDirectory, context, new Foo());\n```\n\nThe Minum database keeps its data and processing primarily in memory\nbut persists to the disk.  Data is only _read_ from disk at startup.\n\nThere are pros and cons to this design choice: on the upside, it's\nvery fast and the data stays strongly typed.  On the downside, it does\nnot make the same guarantees as ACID-compliant databases. Also, an\nill-considered application design could end up using too much memory.\n\nOn the [Memoria project](https://github.com/byronka/memoria_project/),\nthe risks and benefits were carefully considered, and so far it has\nworked well.\n\nUsers are free to pick any other database they desire (See \"Alternate\ndatabase\" project above for an example project using a third-party\ndatabase). \n\n\u003chr\u003e\n\n_Checksum_\n\nOn the `DbEngine2` database engine (the recommended engine for most users),\nthere is a \"checksum\" feature.  In short, a hash will be generated on data\nwhen written, and checked against the file when read.  This confirms the\ndata has not changed since it was written, which could indicate data\ncorruption.  If that is discovered, an exception will be thrown when the\ndata is loaded.  For that reason, it is recommended to use the following\npattern when instantiating (note the `loadData` at the end):\n\n```Java\nAbstractDb\u003cFoo\u003e db = new DbEngine2\u003c\u003e(foosDirectory, context, new Foo()).loadData();\n```\n\nIf registering indexes (see the section on _indexes_ later on), this pattern is recommended:\n\n```Java\nAbstractDb\u003cFoo\u003e db = new DbEngine2\u003c\u003e(foosDirectory, context, new Foo())\n        .registerIndex(\"id\", x -\u003e x.getId().toString())\n        .loadData();\n```\n\n_Adding a new object to a database_:\n\n```java\n// instantiate an object that implements DbData. Note that because\n// this is a new object, we set the index to 0.\nFoo myFoo = new Foo(0L, 42, \"blue\");\n\n// write it to the database.  The returned value is the object we wrote\n// with its index set by the database.\nFoo myResultingFoo = db.write(myFoo);    \n```\n\n_Updating an object in a database_:\n\n```java\nmyResultingFoo.setColor(\"orange\");\ndb.write(myResultingFoo);    \n```\n\n_Deleting from a database_:\n\n```java\ndb.delete(myResultingFoo);    \n```\n\n_Getting a bit more advanced - indexing_:\n\nIf there is a chance there might be a large amount of data and search\nspeed is important, it may make sense to register indexes.  This can\nbe best explained with a couple examples.\n\n1) If every item has a unique identifier, like a UUID, then this is how you would register the index\n   for much-increased performance in getting a particular item (compared to a worst-case full table scan)\n\nIn the following example, we will have a database of \"Foo\" items, each of which has a UUID identifier.\n```java\n// The first parameter is the name of the index, which we will refer to elsewhere when \n// asking for our data, and the second parameter is the function used to generate the key\n// for the internal map.\ndb.registerIndex(\"id\", x -\u003e x.getId().toString())\n\n// later to get the data, it will look like this, and will return\n// a collection of data.  If the identifier is truly unique, then we can \n// prefer to use the findExactlyOne method\ndb.findExactlyOne(\"id\", \"87cfcbc1-5dad-4dcd-b4dc-7d8da9552ffc\");\n```\n\n2) alternately, instead of having one-to-one unique values, we might be partitioning the data\n    in some way. For example, the data may be categorized by color.\n\n```java\n// note that the key generator (the second parameter here) must always return a string\ndb.registerIndex(\"colors\", x -\u003e x.getColor().toString())\n\n// later to get the data, it will look like this, and will return\n// a collection of data.  If the identifier is truly unique, then we can \n// prefer to use the findExactlyOne method\nCollection\u003cFoo\u003e blueFoos = db.getIndexedData(\"colors\", \"blue\");\n```\n\nLogging\n-------\n\n_Writing a log statement_:\n\n```java\n// useful info for comprehending what the system is doing, non-spammy\nlogger.logDebug(() -\u003e \"Initializing main server loop\"); \n```\n\nThe logs are output to \"standard out\" during runtime.  This means, if\nyou run a Minum application from the command line, it will output its\nlogs to the console.  This is a typical pattern for servers.\n\nThe logs are all expecting their inputs as closures - the pattern is\n`() -\u003e \"hello world\"`.  This keeps the text from being processed until\nit needs to be.  An over-abundance of log statements could impact the\nperformance of the system.  By using this design pattern, log\nstatements will only be run if necessary, which is a great performance\nimprovement for trace-level logging and log statements which include\nfurther processing (e.g. `_____ has requested to _____ at _____`).\n\nOther levels of logs use similar methods:\n\n```java\n// useful like \"debug\", but spammy (for example, code in an inner loop)\nlogger.logTrace(() -\u003e \"Socket was closed\");\n\n// logs related to business situations, like a user authenticating\nlogger.logAudit(() -\u003e \"user \\\"foo\\\" has logged in\");\n\n// when something has broken, unexpectedly\nlogger.logAsyncError(() -\u003e \"IOException: error while reading file\");\n```\n\nIt is also possible to programmatically adjust what levels are being\noutput by using the `getActiveLogLevels` method.  For example:\n\n```java\n// disable all logging\nfor (var key : logger.getActiveLogLevels().keySet()) {\n    logger.getActiveLogLevels().put(key, false);\n}\n\n// enable one particular log level\nlogger.getActiveLogLevels().put(LoggingLevel.TRACE, true);\nlogger.logTrace(() -\u003e \"Now you can see trace-level logs\");\n```\n\nHTML Parser\n-----------\n\n```java\nList\u003cHtmlParseNode\u003e results = new HtmlParser().parse(\"\u003cp\u003e\u003c/p\u003e\");\n```\n\nMinum includes a simple HTML parser.  While not as fully-featured as its big brothers, it is well suited\nfor its minimal purposes, and provides capabilities like examining returned HTML data or for use in functional tests.\nIt is used heavily in the [Memoria tests](https://github.com/byronka/memoria_project/blob/c474040aac46b52bc48341b5972c8d9d1c438da8/src/test/java/com/renomad/inmra/featurelogic/FunctionalTests.java#L165)\nand the [FamilyGraph class](https://github.com/byronka/memoria_project/blob/master/src/main/java/com/renomad/inmra/featurelogic/persons/FamilyGraph.java) which\nhandles building a graph of the family members.\n\n\u003chr\u003e\n\n_Searching for an element in the parsed graph_:\n\n```java\nHtmlParseNode node;\nList\u003cHtmlParseNode\u003e results = node.search(TagName.P, Map.of());\n```\n\nEndpoints\n---------\n\nWhen a user visits a page, such as \"hello\" in\n`http://mydomain.com/hello`, the web server provides data in response.\n\nOriginally, that data was a file in a directory. If we were providing\nHTML, `hello` would have really been `hello.html` like\n`http://mydomain.com/hello.html`.  \n\nDynamically-generated pages became more prevalent, and patterns\nchanged.  In Minum, it is possible to host files in that original way,\nby placing them in the directory configured for \"static\" files - the\n`minum.config` file includes a configuration for where this static\ndirectory is located, `STATIC_FILES_DIRECTORY`.  In web applications,\nit is still useful to follow this pattern for files that don't change,\nsuch as JavaScript or images, but also very powerful to provide paths\nwhich return the results of programs, as follows:\n\n```java\nwebFramework.registerPath(GET, \"hello\", sd::helloName);\n```\n\nWith that, there would be a path \"hello\" registered, so that users\nvisiting `http://mydomain.com/hello` would receive the result of\nrunning a program at `helloName`.  Here is simplistic example of what\ncode could exist there:\n\n```java\n    /**\n     * a GET request, at /hello?name=foo\n     * \u003cp\u003e\n     *     Replies \"hello foo\"\n     * \u003c/p\u003e\n     */\n    public IResponse helloName(IRequest request) {\n        String name = request.getRequestLine().queryString().get(\"name\");\n        return Response.htmlOk(\"hello \" + name);\n    }\n```\n\nOne user had a good question about the difference between the patterns\nin more conventional annotation-based frameworks and this one.  See\nthat question [here](https://github.com/byronka/minum/discussions/19)\n\nThe [Quick start guide](docs/quick_start.md) walks through all this in\na bit more detail.\n\nTemplates\n---------\n\n```java\nTemplateProcessor myTemplate = TemplateProcessor.buildProcessor(\"hello {{ name }}\");\nString renderedTemplate = myTemplate.renderTemplate(Map.of(\"name\", \"world\"));\n```\n\nThe Minum framework is driven by a paradigm of server-rendered HTML,\nwhich is performant and works on all browsers.\n\nThe templates can be any string, but the design was driven concerned with rendering HTML \ntemplates.  Here is [an example of a simple template](https://github.com/byronka/minum/blob/master/src/test/webapp/templates/sampledomain/name_entry.html),\nwhich is rendered with dynamic data in [this class](https://github.com/byronka/minum/blob/master/src/test/java/com/renomad/minum/sampledomain/SampleDomain.java) \n\nHTML templates must consider the concept of _HTML sanitization_, to prevent cross-site \nscripting (XSS), when the data is coming from a user.  To sanitize content of an HTML \ntag (e.g. `\u003cp\u003e{{user_data}}\u003c/p\u003e`), sanitize the data with `StringUtils.safeHtml(userData)`, and \nfor attributes (e.g. `class=\"{{user_data}}\"`), use `StringUtils.safeAttr(userData)`.  Putting this all together:\n\n_template:_\n\n```html\n\u003cdiv\u003e{{user_name}}\u003c/div\u003e\n\n\u003cdiv data-name=\"{{user_name_attr}}\"\u003ehello\u003c/div\u003e\n```\n\n_code:_\n\n```java\nString renderedTemplate = myTemplate.renderTemplate(\n  Map.of(\n    \"user_name\", StringUtils.safeHtml(username),\n    \"user_name_attr\", StringUtils.safeAttr(username)\n  ));\n```\n\nA bit more advanced usage: instead of providing a `Map` as the parameter, you can provide\na `List` of `Map`, like follows.  This will render the template multiple times, once for\neach map, and then render them all to one string:\n\n```java\nList\u003cMap\u003cString,String\u003e\u003e data = figureOutSomeData();\n\nString renderedTemplate = myTemplate.renderTemplate(data);\n```\n\n\nUser Input\n----------\n\nIt is a common pattern to get user data from requests by query string or body.\nThe following examples show this:\n\n_Getting a query parameter from a request_:\n\n```java\nString id = r.requestLine().queryString().get(\"id\");\n```\n\n_Getting a body parameter from a request, as a string_:\n\n```java\nString personId = request.body().asString(\"person_id\");\n```\n\n_Get a path parameter from a request as a string_:\n\n```java\nPattern requestRegex = Pattern.compile(\".well-known/acme-challenge/(?\u003cchallengeValue\u003e.*$)\");\nfinal var challengeMatcher = requestRegex.matcher(request.requestLine().getPathDetails().isolatedPath());\n// When the find command is run, it changes state so we can search by matching group\nif (! challengeMatcher.find()) {\n    return new Response(StatusLine.StatusCode.CODE_400_BAD_REQUEST);\n}\nString tokenFileName = challengeMatcher.group(\"challengeValue\");\n```\n\nThis more complicated scenario shows handling a request from the LetsEncrypt ACME challenge \nfor renewing certificates.  Because the incoming request comes as a \"path parameter\", we\nhave to extract the data using a regular expression.\n\nIn this example, if we don't find a match, we return a 400 error HTTP status code, and \notherwise get the data by a named matching group in our regular expression.\n\nTo register an endpoint that allows \"path parameters\", we register a partial path, like the \nfollowing, which will match if the provided string is contained anywhere in an incoming URL. There \nare some complications to matching this way, so it is recommended to use this approach\nas little as possible.  In the Memoria project, this is only used for \nLetsEncrypt, which requires it.  All other endpoints get their user data from query \nstrings, headers, and bodies.\n\n```java\nwebFramework.registerPartialPath(GET, \".well-known/acme-challenge\", letsEncrypt::challengeResponse);\n```\n\n\u003chr\u003e\n\n_Getting a body parameter from a request, as a byte array_:\n\n```java\nbyte[] photoBytes = body.asBytes(\"image_uploads\");\n```\n\nThe photo bytes example is seen in the [UploadPhoto class](https://github.com/byronka/minum/blob/master/src/test/java/com/renomad/minum/sampledomain/UploadPhoto.java)\n\nTesting\n-------\n\nAutomated testing is a big topic, but here I will just show some examples of Minum's code.\n\n_Asserting true_:\n\nCheck that a value is true.  This is useful for testing predicates, and also for more\ncomplex tests.  It is useful and important to include messages that will explain the assertion\nand provide clarity when tests fail.  For example:\n\n```java\nint a = 2;\nint b = 3;\nboolean bIsGreater = b \u003e a;\nTestFramework.assertTrue(bIsGreater, \"b should be greater than a\");\n```\n\nOr, perhaps, an example might be confirming a substring is \ncontained in a larger string:\n\n```java\nString result = \"a man, a plan, a canal, panama!\";\n\nTestFramework.assertTrue(result.contains(\"a plan\"));\n```\n\n_Asserting equal_:\n\nThe other assertion that is widely used is `TestFramework.assertEquals`.  If code is built carefully\n(and especially if using test-driven development) the result of a method can often be verified by\nconfirming it is identical to an expected value. \n\nOne interesting differentiator between this and other Java assertion frameworks is that when `assertEquals` fails, it just shows\nthe value of left and right.  It does not distinguish between which was \"expected\" and \"actual\". This\nprovides a clarity benefit in many cases - sometimes you just want to confirm two things are equal.\n\n```java\nimport com.renomad.minum.testing.TestFramework;\n\nint a = 2;\nint b = 3;\nint c = a + b;\nTestFramework.assertEquals(c, 3);\n```\n\n_Checking for a log message during tests_:\n\nA handy feature of the tests is the `TestLogger` class which extends `Logger`.  If you review its\ncode you will see that it stores the logs in a data structure, and makes access available to recent log\nmessages.  Sometimes, you need to test something that causes an action far away, where it is hard\nto directly assert something about a result - in that situation, you can use the following `doesMessageExist`\nmethod on the `TestLogger` class to confirm a particular log message was output.\n\n```java\nTestFramework.assertTrue(logger.doesMessageExist(\"Bad path requested at readFile: ../testingreadfile.txt\"));\n```\n\n_Initializing context_:\n\nOn that same note, nearly all tests with Minum will need an instance of the `Context` class.  The \nexpected standard way is to run `TestFramework.buildTestingContext` before the test, and then\nrun `TestFramework.shutdownTestingContext` afterwards to close down resources cleanly.  Check\nthose methods more closely to see for yourself.\n\nHere is a very typical example:\n\n```java\n    private static Context context;\n    private static TestLogger logger;\n\n    @BeforeClass\n    public static void init() {\n        context = buildTestingContext(\"unit_tests\");\n        logger = (TestLogger) context.getLogger();\n    }\n\n    @AfterClass\n    public static void cleanup() {\n        shutdownTestingContext(context);\n    }\n```\n\nOutput\n------\n\nHow data is sent to the user is also an important detail.  In many cases, the system will\nsend an HTML response.  Other times, a JPEG image or an .mp3 audio or .mp4 video.  These are\nall supported by the system.\n\nWhen sending data, it is important to configure the type of data, so that the browser\nknows how to handle it.  This kind of information is provided in the\nheaders part of the response message, specifically the \"Content-Type\" header.  The standard for\nfile types is called \"MIME\", and some examples are `text/html` for HTML documents\nand `image/png` for .png image files.\n\nThe `Response.java` class includes helper methods for sending typical data, like the `htmlOk()` method\nwhich sends HTML data with a proper content type.  Here is a method from the `AuthUtils.java` test class\nthat uses a couple methods from `Response`:\n\n```java\npublic IResponse registerUser(IRequest request) {\n    final var authResult = processAuth(request);\n    if (authResult.isAuthenticated()) {\n        return Response.buildLeanResponse(CODE_303_SEE_OTHER, Map.of(\"Location\",\"index\"));\n    }\n\n    final var username = request.getBody().asString(\"username\");\n    final var password = request.getBody().asString(\"password\");\n    final var registrationResult = registerUser(username, password);\n\n    if (registrationResult.status() == RegisterResultStatus.ALREADY_EXISTING_USER) {\n        return Response.buildResponse(CODE_401_UNAUTHORIZED, Map.of(\"content-type\", \"text/html\"), \"\u003cp\u003eThis user is already registered\u003c/p\u003e\u003cp\u003e\u003ca href=\\\"index.html\\\"\u003eIndex\u003c/a\u003e\u003c/p\u003e\");\n    }\n    return Response.buildLeanResponse(CODE_303_SEE_OTHER, Map.of(\"Location\",\"login\"));\n}\n```\n\nA more advanced capability is sending large files, like streaming videos. Minum supports streaming\ndata output.  See [createOkResponseForLargeStaticFiles](https://github.com/byronka/minum/blob/7e1d83fb4927e5ca2f1d3231a21284c8de58f0f4/src/test/java/com/renomad/minum/sampledomain/ListPhotos.java#L237)\nin the `ListPhotos.java` file of the tests.\n\nHelpful utilities\n-----------------\n\n- [Intellij Idea](https://www.jetbrains.com/idea/), an integrated development environment (IDE)\n- [JProfiler](https://www.ej-technologies.com/jprofiler), a Java profiler\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyronka%2Fminum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbyronka%2Fminum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbyronka%2Fminum/lists"}