Choosing the Tech Stack
The Underlying Technical Decisions
Spec CLI is a highly technical project, best categorized as a developer tool. Consequently, many technical considerations were made in the very earliest phases. Nevertheless, all details are now to be compiled here once more and, where applicable, specified more concretely.
The most critical aspect for Spec CLI is absolute transparency in the generated code. Only this ensures everything can be easily understood and extended. There is no hidden magic—just clearly formulated code. Furthermore, this code should manage with as few external dependencies as possible. The goal is to work with the standard library wherever feasible. Since the initial focus is on Go, a very solid foundation is available.
Because the aim is not to generate Go code from TypeSpec, but rather a JSON IR, I consciously avoid the official emitter framework. I want to keep things as pure as possible from the outset. While this might make some parts slightly more extensive, it eliminates an additional layer of abstraction—an important aspect for me. The JSON IR format is simple, readable, and can even be created manually. There are no inheritances or embeddings. Just meaningful JSON that is also very easy to validate.
Thanks to go/ast, go/token, go/parser, go/printer, and go/format, the generation of Go code is accomplished
completely without external dependencies. The approach dictates that exclusively new code is generated—existing code is
not modified. Therefore, the actual handler implementation is placed in separate files. This allows fully generated
files to be clearly marked as such—for example, with a // Code generated by Spec CLI – DO NOT EDIT header—which also
provides the necessary feedback in IDEs.
Validation is about ensuring the API does not accept invalid data and reacts with an error accordingly. Since payload contents must be checked, validation should happen very close to the transport layer. The payloads themselves remain pure structures without logic. Unmarshalling and marshalling are essential for them, so validation is built in right there. This avoids complex structures, and the validation check cannot be bypassed. The corresponding code is generated in full—any subsequent modification would have to be intentional.
Implementation and Testing Approach
The mock server and examples will initially be implemented using http.ServeMux. A consistent step to keep additional
dependencies at bay. The handlers are, in any case, standard-compliant, so using alternative routers is not a problem.
Handler tests are conducted with net/http/httptest.
For the CLI itself, I deliberately rely on proven libraries: primarily spf13/cobra and spf13/viper. Here, the
standard library is clearly at a disadvantage, and a custom implementation would be unnecessarily laborious. The point
is not to never use external libraries—but to use them deliberately and sensibly. That is precisely the case here.
Summary of Technical Guiding Principles
In summary, the most decisive framework conditions are thus met: code is not created via templates or string concatenation. No heavy framework is used for HTTP routing, and even with TypeSpec, I forego the emitter framework. The generated code is self-contained, does not rely on any external library, and offers complete transparency.
With these decisions, nothing stood in the way of the actual project setup.
All these technical decisions—stdlib-only, no emitter framework, AST over templates, minimal dependencies—serve a single purpose: the generated code should feel as if it were written by hand. This was central from the very beginning, even when the idea was still “just” a CLI tool like the JetBrains HTTP Client. Now that the project has grown into a complete spec ecosystem, these principles pay off even more: transparency, maintainability, and trust in one’s own code—exactly what I’ve always wanted.