minum
When you need the fewest moving parts
The simplest Minum program (see more code samples below):
public class Main {
public static void main(String[] args) {
var minum = FullSystem.initialize();
var wf = minum.getWebFramework();
wf.registerPath(GET, "",
r -> Response.htmlOk("<p>Hi there world!</p>"));
minum.block();
}
}
What is this?
This web framework, "Minum", provides a full-powered minimalist foundation for a web application. For TDD, by TDD.
- Has its own web server, endpoint routing, logging, templating engine, html parser, assertions framework, and database
- Around 100% test coverage that runs in 30 seconds without any special setup (
make test_coverage
) - Nearly 100% mutation test strength using the PiTest tool. (
make mutation_test
) - Relies on no dependencies other than the Java 21 SDK - i.e. no Netty, Jetty, Tomcat, Log4j, Hibernate, MySql, etc.
- Well-documented
- Uses no reflection
- Requires no annotations
- No magic
- Has examples of framework use:
- a tiny project, as the basis to get started
- a small project, showing some minimal use cases
- a full application demonstrating realistic usage
Minum is five thousand lines of code - the "minimalist" competitors range from 400,000 to 700,000 lines when accounting for their dependencies. I have not found a similar project.
Applying a minimalist approach enables easier debugging, maintainability, and lower overall cost. Most frameworks trade faster start-up for a higher overall cost. If you need sustainable quality, the software must be well-tested and documented from the onset. As an example, this project's ability to attain such high test coverage was greatly enabled by the minimalism paradigm.
Getting Started
There is a 🚀 Quick start, or if you have a bit more time, consider trying the tutorial
Maven
<dependency>
<groupId>com.renomad</groupId>
<artifactId>minum</artifactId>
<version>8.0.1</version>
</dependency>
Features:
- Secure TLS 1.3 HTTP/1.1 web server
- In-memory database with disk persistence
- Server-side templating
- Logging framework
- Testing framework
- HTML parsing
- Background queue processor
Size Comparison:
Compiled size: 200 kilobytes.
Lines of production code (including required dependencies)
Minum | Javalin | Spring Boot |
---|---|---|
5,217 | 141,048 | 1,085,405 |
See details
Performance:
- 19,000 http web server responses per second. detail
- 2,000,000 database updates per second. detail
- 31,717 templates rendered per second. See "test_Templating_Performance" here. Also, see this comparison benchmark, with Minum's code represented here.
See framework performance comparison
Documentation:
Example projects demonstrating usage:
See the following links for sample projects that use this framework.
This project is valuable to see the minimal-possible application that can be made. This might be a good starting point for use of Minum on a new project.
This is a good example to see a basic project with various functionality. It shows many of the typical use cases of the Minum framework.
This is a family-tree project. It demonstrates the kind of approach this framework is meant to foster.
Code samples
Instantiating a new database:
var db = new Db<>(foosDirectory, context, new Foo());
Adding a new object to a database:
var foo = new Foo(0L, 42, "blue");
db.write(foo);
Updating an object in a database:
foo.setColor("orange");
db.write(foo);
Deleting from a database:
db.delete(foo);
Writing a log statement:
logger.logDebug(() -> "hello");
Parsing an HTML document:
List<HtmlParseNode> results = new HtmlParser().parse("<p></p>");
Searching for an element in the parsed graph:
HtmlParseNode node;
List<HtmlParseNode> results = node.search(TagName.P, Map.of());
Creating a new web handler (a function that handles an HTTP request and returns a response):
public Response myHandler(Request r) {
return Response.htmlOk("<p>Hi world!</p>");
}
Registering that endpoint:
webFramework.registerPath(GET, "formentry", sd::formEntry);
Building and rendering a template:
TemplateProcessor foo = TemplateProcessor.buildProcessor("hello {{ name }}");
String rendered = foo.renderTemplate(Map.of("name", "world"));
Getting a query parameter from a request:
String id = r.requestLine().queryString().get("id");
Getting a body parameter from a request, as a string:
String personId = request.body().asString("person_id");
Get a path parameter from a request as a string:
Pattern requestRegex = Pattern.compile(".well-known/acme-challenge/(?<challengeValue>.*$)");
final var challengeMatcher = requestRegex.matcher(request.requestLine().getPathDetails().isolatedPath());
// When the find command is run, it changes state so we can search by matching group
if (! challengeMatcher.find()) {
return new Response(StatusLine.StatusCode.CODE_400_BAD_REQUEST);
}
String tokenFileName = challengeMatcher.group("challengeValue");
Getting a body parameter from a request, as a byte array:
byte[] photoBytes = body.asBytes("image_uploads");
Checking for a log message during tests:
assertTrue(logger.doesMessageExist("Bad path requested at readFile: ../testingreadfile.txt"));