Spec <-- api code is far superior to Spec --> api code, imo.
Feels like it's going backwards - there's really no reason why it has to be a .tsp, instead of a .ts with actual api code. It's even using @annotations. In fact the annotations i see in the screenshot (@route, @query, @path) are practically the same in NestJS.
I feel that we should be focusing on enhancing that paradigm instead. In fact I already have a working POC of NestJS -> OpenAPI -> Client libraries so I see no place for this. The spec itself is simply a vehicle for code generation, and serves little purpose otherwise and I'd be happy to be rid of it.
Basically my PoV. The API code itself is the best possible documentation.
Not to mention, how else do you see what complex logic might happen in an endpoint?
It seems typespec deals only with extremely simple CRUD APIs, for which again just reading the code would be good enough.
In scenarios where you want to offer the API consuming team some mock, I'd argue time would be better spent providing them with a a json-server implementation (see: https://www.npmjs.com/package/json-server).
If you can define single spec and autogenerate everything (client/server/OpenAPI et. al.), then spec first is superior.
Looking at https://smithy.io/2.0/index.html which can already generate much more than TypeSpec based on the docs and awesome list.
I'm not sure how I can autogenerate non-trivial logic, which is my point.
How would you handle a typical case where based on request data an endpoint fetches some additional information from various sources and depending on that performs several different actions?
This is the most common scenario I've encountered outside of extremely trivial CRUD endpoints.
EDIT: don't get me wrong, I'm not being purposefully obtuse - I was a big swagger advocate back in the day, however over time I came to realize that effort was much better invested in writing clear API code and possibly a mock impl like json-server.
Smithy is _very_ similar to Coral (an internal library within Amazon).
Coral is used by every AWS service -- every AWS service defines their APIs using Coral, and if you want to interact with another service you use a generated client.
The client can generally be used as-is, though sometimes you might want some custom client code. In the case of a generated client you can just grab the API definition of the API you want to call, generate the client in the language of your choice, and... that's it!
For the server, you still have to implement the logic of how to form the responses, but what the request/responses look like it enforced by the framework.
Here's an example: https://gist.github.com/shepherdjerred/cb041ccc2b8864276e9b1...
I'm leaving out a _lot_ of details. Coral is incredibly powerful and can do a lot of things for you for free. Smithy is, from what I can see, the same idea.
I am still trialing Smithy, but as far as I understand, the code which is generated by Smithy, generates suitable abstractions and you never modify this code yourself.
It leaves the middleware library selection for the user, and with middleware you can do whatever more complex operations you need.
TypeScript server overview: https://smithy.io/2.0/ts-ssdk/introduction.html
The Service type itself also seems to make it possible to define quite complex logic: https://smithy.io/2.0/spec/service-types.html
Hmm. So as I understand it, it generates handler definitions which you then implement for typesafety but routing/hooking up is an implementation detail?
What if you need to generate two server implementations (possibly in different languages) that adhere to the same specification?
You don’t always go server -> spec -> client.
That seems like a rare use case. Surely we optimise for the general use case - you start working on a new server powered platform, and write it once in your stack of choice, and release libraries for clients (mobile apps, integrations, web apps).
Even so, a e2e test suite would surely serve far more utility over a spec that simply stubs out endpoints with no functionality.
It is not at all uncommon to want a spec for an api that’s out of your control. Or to want to define shared data structures at the transport layer which may have “owners” in heterogenous languages. Spec languages are (well, can be—so very many are terrible) very nice in those cases, which are not at all rare. They may well be more common than the “we’re writing a new single-language monolith that is free to control the shape of the data it slings” use case.
for internally consumed APIs I somewhat agree, but if you wanna expose APIs to external developers it usually does pay off to spend a bit of time on the API design itself, what APIs would you expect to see if you were the user of the your own service - and this is where tools like this really help imo - you could even preview your API in some openapi doc tool like readme or similar, in which case it's like previewing your product before releasing it
Spec-first approach works better when client and server teams work on their parts simultaneously and need some contract before the implementation starts.
I can see the appeal there, but I can only imagine it's utility diminishes quickly over time as product evolves, and probably doesn't survive past implementation kickoff. Last thing developers love to do is to have to update a spec after having to update multiple tests and server code.
If it does become a long lived artifact, CI/CD must also be a nightmare, having to figure out which commit matches which version of the specification, since the spec is now a distinct artifact from it's documented target, and similar which version of the client. A literal "3 body problem".
On the other hand, if you already have a project template (granted, you do need to fight through all of the various build-time configuration required to get a typescript project up and running) you could probably achieve the same by simply stubbing the API endpoints in code to generate the spec.
If there was an advantage to a spec first model, it would be that any change to the api would be a conscious change, and highly visible. I've also encountered situations where a innocuous refactor (changing a class name or method name) broke the previous builds. But one could potentially integrate a gate into CI/CD by diffing the outputs of generated specs.
Much of my opinion on this subject is based on my own experience using Postman as a pre-implementation spec. But conceptually I see the same problems arising from any spec-first approaches.
You describe problems that were solved long time ago and not even in IT. Spec-first approach works fine in the long run, it just requires a bit more process maturity than you can find in a typical startup.
For example, the problem of matching commits with specs doesn’t even exist in environments without continuous deployment (which is rarely a real necessity and often is even undesirable). You just tag your releases in VCS (can be easily automated) and track their scope in documentation (job of responsible product and engineering managers which know what goes live and when).
the whole point of a DSL for APIs is so it can interop with different languages, frameworks and toolchains. Sure if you all your services are NestJS then you don't need this.