XRAPH/Writing/Four backend frameworks in seven years

Four backend frameworks in seven years

A TypeScript template, two Rust frameworks and one in Go. What each got wrong in order, and the two ideas that survived all of them.

Published
Dec 2025
Length
2 min read
Systems
4

#The sequence

Ultimate Backend in 2019, a TypeScript template. Ultimate in 2020 and North in 2021, both Rust frameworks. Forge in 2025, in Go. Three of the four are unmaintained, and I would write all of them again in the same order.

#What each got wrong

Ultimate Backend was a template. You clone it and from that moment your copy and the original diverge. Every later improvement was unavailable to anyone already using it. The distinction between a template and a dependency is the whole lesson and it took me years to act on, because a template is much easier to write. A template can assume anything about the application. A dependency has to be right about the seams.

Ultimate leaked its abstractions into type signatures. A handler resolving two services from a container had four generic parameters, two lifetimes and a trait bound wider than my editor. The module did not hide its decisions, it re-exported them .

North fixed the lifecycle and not the ergonomics. Startup as a dependency graph was the best idea in either Rust framework and is load bearing in everything I have built since. The signatures were still unreadable to anyone who had not written them.

Forge started with an interface that was too large. Seven required methods on the extension interface, most satisfied with empty bodies. Corrected to one required method with optional capabilities. I made the same mistake three times, which suggests it is not obvious.

#The two ideas that survived

Lifecycle as a declared graph. Components declare dependencies, the container computes a start order, shutdown reverses it, and the graph can be printed. Debugging a startup failure becomes reading a list instead of a stack trace.

Errors that carry the operation. "connection refused" is not actionable. "starting postgres pool for service assets: connection refused" is. Rust culture taught me this and Go kept it.

#What I would tell someone starting a framework

Count how many methods of your main interface are usually empty. A high number is a design defect rather than a documentation problem.

Write the quick start before the implementation. If it does not fit in fifteen lines, the abstraction is wrong, and finding that out before writing the code is considerably cheaper.

Be honest about who it is for. A framework for people writing services should optimise for their constraints rather than the author's aesthetics, and those diverge more than you would like .

#Whether it was a good use of seven years

The frameworks themselves are worth less than what building them taught me about the shape of the problem. Brooks separates essential from accidental difficulty , and most of what I was fighting in the first three was accidental: languages, ergonomics, my own preferences.

The essential difficulty is service lifecycle under partial failure, and it turned out to be the same problem in all four languages . The fourth framework is better because I stopped solving the accidental part.

References

  1. [1]D. L. Parnas, On the Criteria To Be Used in Decomposing Systems into Modules, Communications of the ACM, vol. 15, no. 12, pp. 1053-1058, 1972doi:10.1145/361598.361623
  2. [2]Frederick P. Brooks Jr., No Silver Bullet: Essence and Accidents of Software Engineering, Computer, vol. 20, no. 4, pp. 10-19, 1987doi:10.1109/MC.1987.1663532
  3. [3]Melvin E. Conway, How Do Committees Invent?, Datamation, vol. 14, no. 5, pp. 28-31, 1968
  4. [4]Sam Newman, Building Microservices: Designing Fine-Grained Systems, O'Reilly Media, 2nd edition, 2021