iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Building Mach, a C++20 web framework, taught Asaf Dahan four practical lessons: make convenience intentional, draw clear boundaries around abstractions and object lifetimes, test the cases that can defeat apparently correct code, and defer features when release timing makes them a poor fit. These are lessons from one project, not a universal prescription for framework design.
1. A simple API takes deliberate work
Dahan wanted Mach handlers to accept request data without imposing an arbitrary rule about parameter order. In his account, handlers could take no arguments, a request body, a context, or both the body and context in either order. Mach inspected handler signatures at compile time, then bound incoming data and invoked the handler at runtime.
The convenience at the call site depended on more machinery inside the framework. Dahan considered that trade-off worthwhile: users did not have to remember a parameter-order convention chosen for implementation convenience.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →He also describes a validation builder. A request type provides a validate() method, fields are identified through member pointers, and rules such as minimum or maximum length can be declared. Mach then validates the request body and returns a 400 response with errors when validation fails. These are details of Mach’s implementation as Dahan describes it, not capabilities provided automatically by C++.
#1 Best Overall
2. Abstractions and ownership need boundaries
Choose where an abstraction earns its cost
Dahan used PImpl for AppBuilder to separate its public interface from its implementation. The implementation uses a unique_ptr, which adds a heap allocation. He accepted that cost for an object involved in application setup, while being more cautious about adding it to an object created for every request. This was a design judgment, not a reported benchmark.
Make lifetime relationships explicit
During development, Dahan learned to distinguish objects that are stored, borrowed, transferred, or shared. He used unique_ptr extensively and shared_ptr when shared lifetime made sense, including asynchronous operations. He says he finished Mach without writing an explicit new or delete; that describes his experience with this project, not a rule that manual allocation is always wrong.
Reuse infrastructure when it serves the project
Mach used Boost.Asio and Boost.Beast for asynchronous networking and HTTP, and spdlog for logging. Dahan’s stated rationale was to focus his effort on Mach’s architecture and behavior instead of rebuilding substantial infrastructure.
3. Challenge code that feels correct
Let route matching reconsider a choice
Dahan gives a router example with two registered routes: /a/{param}/c and /a/b/d. A request for /a/b/c can expose a failure in a traversal that tries the static b branch first: that branch eventually reaches a dead end, although the parameterized route matches. His fix was to backtrack—try the preferred static path, then try the parameterized path if the first choice cannot complete.
As Dahan puts it, “Recursion was how I solved the problem; the important missing behavior was the ability to reconsider an earlier choice.”
Reject ambiguous route definitions
Some patterns should be rejected before serving requests. For example, registering both /a/{name}/c and /a/{id}/c for the same method creates ambiguity: the parameter names differ, but the route shapes do not. Dahan connects this kind of edge case to a broader testing habit: consider which combinations could break a system that appears to work, examine failing tests carefully, and remember that tests themselves can contain bugs.
4. Shipping requires timing decisions
Dahan wanted handlers to accept typed route and query parameters. Implementing that feature meant identifying where each parameter came from, validating combinations, and integrating the result into the framework. As the v1.0 release and API freeze approached, he deferred it rather than force the work into the final stretch.
Recommended Free Tools
Deferring the feature did not mean deciding it was impossible or undesirable. It meant keeping the idea available for later instead of adding a substantial design and implementation task at a risky point in the release schedule. Dahan’s article says Mach v1.0 binds request bodies to handler parameters; it does not establish the project’s current release status, compatibility, or maintenance state. Read his account at DEV Community.
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

