OpenAPI v4 (aka Moonwalk) Proposal
github.com
github.com
Here are my gripes:
1) For me one of the biggest selling points is client code gen (https://github.com/OpenAPITools/openapi-generator). Basically it sucks, or at least it sucks in enough languages to spoil it. The value prop here is define the API once, code gen the client for Ruby, Python and Scala (or insert your languages here). Often there are a half dozen clients for each language, often they are simply broken (the generated code just straight up doesn't compile). Of the ones that do work, you get random PRs accepted that impose a completely different ideological approach to how the client works. It really seems like any PR is accepted with no overarching guidance.
2) JSONSchema is too limited. We use it for a lot of things, but it just makes some things incredibly hard. This is compounded by the seemingly limitless number of version or drafts of the spec. If your goal is interop, which it probably is if you are using JSON, you have to go our and research what the lower common denominator draft spec JSONSchema support is for the various languages you want to use and limit yourself to that (probably draft 4, or draft 7).
On the pros side:
It does make pretty docs - kinda wish it would just focus on this and in the process not be as strict, I think it would be a better project.
I think it’s a pretty big problem for many devs that so many of the options are mediocre and they’re quite difficult to evaluate unless you have a lot of experience, and even then it takes a lot of time.
Nswag has important issues that are many years old still in their backlog.
1.6k issues, oldest unresolved 7 years old:
https://github.com/RicoSuter/NSwag/issues?q=is%3Aissue+is%3A...
But it's been nice being able to make a backend change, run the code generator, and then be able to use whatever API in react. I hope this type of stuff gets developed more!
[0] - https://github.com/reduxjs/redux-toolkit/tree/master/package...
But ya... JSONschema is confusing and doesn't really support type composition the way you think it would.
If your API model is simple, then you'll probably have decent experience with clients... But if you need "allOf/anyOf/oneOf" and to restrict "additionalProps", you're probably going to have a rough time...
https://johnnyreilly.com/generate-typescript-and-csharp-clie...
Feel free to email me at sagar@speakeasyapi.dev or join our slack (https://join.slack.com/t/speakeasy-dev/shared_invite/zt-1cwb...) . We're in open beta and working with a few great companies already and we'd be happy for you to try out the platform for free!
The generators are open source: https://github.com/fern-api/fern
We rewrote the code generators from scratch in the language that they generate code in (e.g., the python generator is written in python). We shied away from templating - it's easier but the generated code feels less human.
Want to talk client library codegen? Join the Fern Discord: https://discord.com/invite/JkkXumPzcG
It's also worth noting that most JSON Schema replacements I've seen that prioritize code generation are far less powerful in terms of runtime validation (I have not examined Fern's proposal in detail, so I do not know if this is true for them).
The ideal system, to me (speaking as the most prolific contributor to JSON Schema drafts-07 through 2020-12), would have clearly defined code generation and runtime validation features that did not get in each other's way. Keywords like "anyOf" and "not" are very useful for runtime validation but should be excluded from type definition / code generation semantics.
This would also help balance the needs of strongly typed languages vs dynamically typed languages. Most JSON Schema replacements-for-code-generation I've seen discard tons of functionality that is immensely useful for other JSON Schema use cases (again, I have not deeply examined Fern).
GraphQL promised us apis that we can trust - since both the client and the server were implemented with the same schema, you would know for sure which requests the api would respond to and how, if it tried to do something outside of the schema, the server lib itself would through a 500 error. This allowed you to generate lean, typesafe clients.
OpenAPI kinda allows you to do that but for any other http api - I’ve written some code to use the schema as a “source of truth” for the server code as well, proving at compile time that the code will do the correct requests and responses for all the endpoints, paths and methods. So if you are reading the schema, you know for sure that the api is going to return this, and any change has to start from modifying the api.
And in turn this allows a “contract first” dev where all parties agree on the api change first, and then go to implement their changes, using the schema as an actual contract.
Combine this with languages with expressive type systems, and it allows you a style of coding thats quite nice - “if it compiles it is guaranteed to be correct”. Now of course this does not catch all bugs, but kinda confines them to mostly business logic errors, and frees you from needing to write tons of manual unit tests for every request.
Oh as a bonus it can be used for runtime request validation as well, which allows you to have types generated for those as well, for the client _and_ the server! Makes changes in the api a lot more predictable.
Client / server code generation can also be implemented as just type generation with no actual code being created, sidestepping a lot of complaints about code generators.
I did package it up as OS https://github.com/ovotech/laminar but no longer have access to maintain it as I no longer work there unfortunately.
Just wanted say that this is very cool and I find it hard to understand why this is not already the norm in 2023. I've done something quite similar in a proprietary project (I called it "spec-driven development" in reference to "test-driven development").
I would first start by writing the OpenAPI spec and response model JSON schema. I could then write the frontend code, for example, as the API it called on the server was now defined. Only as the last step I would actually integrate the API to real data - this was especially nice as the customer in this particular project was taking their time to deliver the integration points.
All the time during development the API conformity was being verified automatically. It saved me from writing a bunch of boilerplate tests at least.
It’s often much more practical to integrate early and then iterate on the implementation and the spec at the same time until reaching a stable point.
What I’ve seen so often (and why I implemented this) was that the spec will be written, found insufficient, and the code updated, leaving the spec out of date.
Having to write the spec first allows you to actually iterate on it before implementing the code, with all the server / test client generators. So the frontend team can start working on its end before the backend even implements anything, as most of its test would be done against mocks / fake data generators.
Even better, since the spec is a source of truth for soo many things - validation, client test requests, server test responses, docs, as well as the paths / endpoints in both client and server implementation, it saves an enormous amount of time and communication energy to have it be implemented in one place.
Unfortunately we don't yet have a "try now" button, and our codegen is still closed-source, but you can see some of the libraries we've generated for companies like Modern Treasury and sign up for the waitlist on our homepage.
Always happy to chat codegen over email etc.
To add my difficulty, the document generation inside Sphinx was less than up to date. Such that I didn't even get the pretty docs.
It saves hours and hours of development time. And the ability to regenerate the whole application on spec changes is amazing.
It is not a specification to define your business logic classes and objects -- either client or server side. Its goal is to define the interface of an API, and to provide a single source of truth that requests and responses can be validated against. It contains everything you need to know to make requests to an API; code generation is nice to have (and I use it myself, but mainly on the server side, for routing and validation), but not something required or expected from OpenAPI
For what it's worth, my personal preferred workflow to build an API is as follows:
1. Build the OpenAPI spec first. A smaller spec could easily be done by hand, but I prefer using a design tool like Stoplight [0]; it has the best Web-based OpenAPI (and JSON Schema) editor I have encountered, and integrates with git nearly flawlessly.
2. Use an automated tool to generate the API code implementation. Again, a static generation tool such as datamodel-code-generator [1] (which generates Pydantic models) would suffice, but for Python I prefer the dynamic request routing and validation provided by pyapi-server [2].
3. Finally, I use automated testing tools such as schemathesis [3] to test the implementation against the specification.
[1] https://koxudaxi.github.io/datamodel-code-generator/
This is still a win because you can still generate all your clients in sync with your API spec rather than doing all that manually.
I agree that the official codegen is not that great. One of my former colleagues started guardrail[0] to offer better client -- and server -- codegen in Scala for a few different http/rest frameworks. Later, I added support for Java and some Java frameworks. (I haven't worked on the project in over a year, but from what I understand, it's still moving forward.)
Obviously that's a fairly limited set of languages and frameworks compared to what the official generators offer, and there are some OpenAPI features that it doesn't support, but guardrail is a good alternative if you're a Java or Scala developer.
> JSONSchema is too limited
I've run into some of the problems you've described, which can be a big bummer. For new APIs I'd designed, I took the approach of designing the API in a way that I knew I could express in OpenAPI without too much trouble, using only the features I knew guardrail supported well (or features I knew I could add support for without too much trouble). It's not really the ideal way to design an API, but after years of that sort of work, I realized one of the worst parts of building APIs is the tedious and error-prone process of building server routes or a client for it, and I wanted to optimize away as much of that as possible.
Ultimately my view is that if you are writing API clients and servers by hand, you're doing it wrong. Even if you end up writing your own bespoke API definition format and your own code generators, that's still better than doing it manually. Obviously, if something like OpenAPI meets your needs, that's great. And even if you don't like the output of the existing code generators, you can still write your own; there are a bunch of parser libraries for the format that will make things a lot easier, and it really isn't that difficult to do, especially if you pare your feature support down to the specifics of what you need.
It's only useful for generating types; most generators' APIs are stubs at best, which means it's pretty much useless for evolving API specifications.
JSON has its limitations, in that its type system is different enough from other languages that back-end generated code often feels awkward.
I think that the foundation should take ownership of the generators and come up with a testing, validation & certification system. Have them write a standardized test suite that can validate a generated client, making sure there's a checklist of features (e.g. more advanced constructs like `oneOf` with a discriminator, enums, things like that).
And they should reduce the number of generators. Have one lead generator for types, then maybe a number of variants depending on what client the user wants to use. But those could be options / flags on the generator.
Of course, taking a step back, maybe OpenAPI and by extension REST/JSON is a flawed premise to begin with; comparing it with e.g. grpc or graphql, those two are fully integrated systems, where the spec and protocol and implementation are much more tightly bound. The lack of tight bounds (or strict standards for that matter) is an issue with REST/JSON/OpenAPI.
Another way of handling this is getting the server your are interacting with to be able to generate the code directly based on their own internal knowledge of how the APIs are put together. This puts more onus on the library creators to support languages etc, but provides a much better experience and better chance things will 'just work' as there are just less moving parts.
ServiceStack is a .NET library that does this with 'Add ServiceStack Reference'[0] which enables a direct generation of Request and Response DTOs with the APIs for the specific server you are integrating with. IDE integration is straight forward since pulling the generated code is just another web service call. Additional language generation are integrated directly. It had trade offs but I'm yet to see a better dev experience.
[0] https://servicestack.net/add-servicestack-reference
(Disclaimer I work for ServiceStack).
Anyone care to suggest alternatives though, assuming we want to call from node to python? I actually believe that having api packages with types is one of the only things startups should take from the enterprise world. I thought about GRPC, I had good experience with it as a developer, but the previous company had a team of people dedicated just to help with the tooling around GRPC and Protobufs.
So I picked OpenAPI, figuring simple is better, and plaintext over http is simpler. and currently I do believe it's better than nothing, but not by much. I am actually in the process of trying to write my own codegen and seeing how far I can get with it.
are protobuf's with GRPC really the way to go nowadays? should a startup of 20 developers just give up and document api in some shared knowledge base and that's it?
https://github.com/RicoSuter/NSwag (It sucks in any OpenAPI yml, not just ones from Swashbuckle/C#)
Checkout out this demo: https://www.loom.com/share/42de542022de4e55a1349383c7a465eb. Feel free to join our discord as well: https://discord.com/invite/JkkXumPzcG.
That said, I didn't like the amount of moving pieces, annotation soup in code, etc. I got rid of all of it. Instead of relying on a fancy developer web portal with automagically updating docs, I am maintaining demo integration projects in repositories our vendors will have access to. I feel like this will break a hell of a lot less over time and would be more flexible with regard to human factors. Troubleshooting OpenAPI tooling is not something I want myself or my team worrying about right now.
For internal projects we use grpc which is a breeze to use in comparison.
Update: per Wikipedia, looks like OpenAPI was founded in 2010-11 so that would make sense https://en.wikipedia.org/wiki/OpenAPI_Specification
paths:
- name: "speakers"
requests:
- name: createSpeaker
method: post
This structure would have allowed adding request name to the schema without breaking everything.This really goes for anyone building REST/JSON APIs. Please avoid dynamic keys; whatever you think the "primary key" is today, it may be different tomorrow. Clients can easily hash an array of objects into a map if they need it.
More broadly, I'm interested in sparse field updates vs. full payload updates and how each of these handle nullable / emptyable fields. I haven't seen any protocol or standard handle these well.
Also, I agree with the person who mentioned JSON Patch (RFC 6902), which I feel is an under-rated and underused technology. While less intuitive than JSON Merge Patch (RFC 7396), it is far more powerful. I have used both together, using JSON Merge Patch where possible to keep things more readable and intuitive, and using JSON Patch where JSON Merge Patch can't do what is needed. Although if most of your changes need JSON Patch, I find it's better to just stick with that.
Worse, too many of the client generation libraries all look to be abandoned. With no real indication for me to know which would have a good future.
I work on a tool in this space called TypeSpec (aka.ms/typespec) that aims to address some of the authoring concerns folks have with OpenAPI. We're a language that feels a lot like TypeScript, with support for high-level features you might be used to in a more typical PL, but compiles to high quality OpenAPI 3.0 you can feed into your existing code/docs generation pipeline. You can see this in action on our playground: https://cadlplayground.z22.web.core.windows.net. We also support protobuf and (once my PR gets merged) JSON Schema targets.
We're not yet to beta (obviously, no website even) but we're hoping to get there relatively soon. Happy to hear any thoughts, especially from folks using OpenAPI.
How do you think about the relation to OpenAPI v3 (i.e., setting aside possible improvements in v4) — is the goal of TypeSpec to avoid doing things that are too far afield from what you can represent in OpenAPI, or is the OpenAPI thing more like a bridge to adoption so people can use their existing generators, but in the future you imagine people generating clients from TypeSpec directly?
That said, these are not opposing choices really, or a bridge to anything. OpenAPI works great for probably most http services, has a huge ecosystem, and enjoys wide support across the industry so I'd expect many folks to continue to leverage our OpenAPI emitter to take advantage of that.
In general we don't limit ourselves to things which can be trivially compiled to OpenAPI but try to be super general purpose. We support protobuf and intend to support more protocols going forward, and also have experimented with generating other things like JSON RPC, ORMs, db migrations, etc.
All this is - trying to do RPC over HTTP in a fashion that was deemed virtuous in some doctoral thesis.
I wish there were better alternatives for RPC that work everywhere including browsers.
EDIT: typos
[0]https://github.com/go-swagger/go-swagger/issues/1122#issueco...
One factor in 3.1 support is that it came out more-or-less concurrently with JSON Schema draft 2020-12, and depends on it. 2020-12 support has recently become more common across more languages, and we're seeing 3.1 work pick up the pace a bit.
But (from years of experience working on these standards), there is _always_ a lag in adoption. You can't just sit and wait until everyone "catches up" because that won't really shorten the lag to the next major version (OAS 3.1, despite the numbering, had significant enough changes to lag like a major version).
So while I'd agree that it's slower than if there were a clear and well-funded owner of things (which is closer to the situation with AsyncAPI), it's not _unusually_ slow as these things go.
What I really want is a way to generate clients from the server source. I realize that this would require a highly opinionated web server with strong typing on all endpoints, but that just sounds like extra value to me.
Are there any web frameworks that enable generating clients like this, whether through a generated OpenAPI spec or otherwise?
I think perhaps you just never realized this has been common practice for a long time...
There are lots and lots of web service frameworks that do that: FastAPI in Python, Spring in Java, Play in Scala (iheartradio/play-swagger), rocket/okapi in Rust, many many more. You just need some introspection it's not a difficult thing to do.
or https://github.com/sukovanej/effect-http ?
there are several others in TS world.
Apparently django-ninja (a different REST framework for Django) also generates an OpenAPI spec but I haven't tried it.
It mostly just works, like you said, and almost acts as a framework guardrail: if the inferred client types are comprehensive and unsurprising then the view tends to be concise; a wonky type indicates there may be something nonstandard in the view that could be fixed by cleaner framework-abiding code.
In my opinion the problem is that there’s some APIs that are impossible to represent with OpenAPI — that’s the real challenge they should be solving with this version, not reducing spec line count.
Bigger orgs also have style guides for public APIs and you can use various linters/standardization tools to enforce this. Tech writers can work on the API documentation without making changes in your code. There are tools that can tell you if your spec changes would cause breaking changes to API clients, such as adding a required query parameter.
You might not need these things on your project, but for some users it makes sense to write the spec first.
My point is, when I work with a framework that generates an OpenAPI spec, I find it faster to generate the spec from a prototype server than to write the spec by hand.
Optic/UseOptic does a similar traffic watch and spec export from local builds.
1. "The primary goal of this proposal for a major new version of OpenAPI is to make it more approachable in order." -> This is a sound objective. I'm glad to see less nested structures that will improve readability. It'll make it easier to scroll the JSON/YAML and follow the logic.
2. "OpenAPI has become the defacto standard for API descriptions." -> With OpenAI's choice to pick OpenAPI as the standard for ChatGPT plugins, this is more true than ever. It's great to see that now giving names to responses will make it easier for AIs (i.e., ChatGPT, Copilot) to call an API more accurately.
3. No mention of improving the quality of codegen (e.g., client libs, server stubs). Surprised that Moonwalk is silent on this topic.
It's not entirely clear to me where things go from here, but I suspect Moonwalk will address it in some way. I'd like to focus on it some (from the OpenAPI perspective more than JSON Schema, specifically) but I haven't found anyone who would sponsor that work (I guess the dollars are flowing more to these alternatives several folks have mentioned)
Hmm first time I see an api try to cator to AI tools rather than the other way around, feels like we’ve turned a corner for AI tools. Wonder what will happen when we start to design code / libraries / languages with this in mind.
I have a feeling there will be a “designed for ai” language that is going to sweep our industry, maybe turn the languages of today in the same place as assembly/forth - nice for some specific applications, but not mainstream…
What we really need is better tooling to help developers maintain the spec and built generators on top of it.
I’ve been building open source API version control tools built on top of OpenAPI. It’s an easy way to keep your spec up-to-date just by looking at test traffic. If it detects a diff, it will update the OpenAPI sort of like a snapshot test. https://www.useoptic.com/cli
It is definitely verbose to write and Moonwalk would help. Co-pilot helps a huge amount, but I do crave a DSL that's not indentation based.
- Use a DSL to describe your service and have it spit out the OpenAPI spec as well as server stubs. In other words, I wouldn't bother writing OpenAPI directly - it's an artifact that is generated at build time. As a Go user, I quite like Goa (https://goa.design/) but there are others shared in here like TypeSpec.
- There are situations where sticking a backend-for-frontend (BFF) in front of APIs can yield great productivity boosts. For example, in the past we built a thin GraphQL proxy that calls out to a poorly structured REST API. Integrating with that was much more convenient. Most recently, I've been playing with a BFF built with tRPC (https://trpc.io/) which calls out to a REST API. It seemed to provide an even better experience if you use TypeScript on the front-end and in the BFF. It does not have a codegen step and I was really pleased with how fast I could iterate with it - granted it was a toy project.
But like others have said, the client code generators leave a lot to be desired. For example, I have yet to find one that properly deals with recursive data structures like an arbitrarily-nested tree structure with multiple node types. Several of them just flat-out crash, and the ones that don't generate code that won't compile or that bombs out. We've had to resort to hacks like tagging the recursive structures' descriptions with magic keywords that trigger a postprocessing step to fix up the generated code.
But I don't really view this as a problem with OpenAPI per se. The data structures in question can be correctly and unambiguously described in the OpenAPI JSON document.
It doesn't sounds like a notable change given a lot of OpenAPI specs are generated and not necessarily read by humans?
This is almost like calling JSON as XML 2.0.
It's great to see a number of alternatives listed in this thread - there's much more active development in this space then I was aware of, and I hope that some of it gets upstreamed back into OpenAPI.
I'll shamelessly plug our tool in this space - Taxi (https://github.com/taxilang/taxilang), which has a dedicated DSL (not YAML) you can either use standalone, or embeddedd within OpenAPI.
I also happen to think that (for internal teams at least), generating clients on ${apiSpec} is a form of tight coupling, where producer and consumer become tied together. If you can avoid it, you should, as it allows producers and consumers to stay loosely coupled and evolve independetly without the gymnastics of avoiding breaking changes.
I've talked about this before, with proposed solutions.[0]
[0]https://orbitalhq.com/blog/2023-01-16-using-semantic-metadat...
I can understand why OpenAPI has those issues though - it is because OpenAPI is aiming to DOCUMENT APIs rather than SPECIFY them. That is a hard task given how wide a set of things you can do with HTTP.
So be charitable when comparing OpenAPI to XYZ API specification tool.
a) involves an incredible amount of DRY in the specs (to the extend we tinkered with a DSL to generate the openapi which feels wrong) b) doesn't have very good ways of splitting large files up and c) is YAML which is horrible. (Yes, JSON too, but that's even worse for authoring).
I can see if you're trying to describe an existing API with OpenAPI there are enhancements here which you will welcome.
If you're using OpenAPI to develop new APIs, I've come to the conclusion of "don't. Use GraphQL". After initially worrying about over-fetching and N+1 stuff, and thinking "yeah GraphQL for the front end, but the serious stuff should be OpenAPI" I've ditched that. GQL Federation was the final nail; it /does/ force some conversations up the chain (how exactly ought two, supposedly completely independent APIs, federate) but in practice this is a useful attribute.
Then, not directly related, you have the problem of each service having to have their own client. Maintenance doubles.
We're going down the graphQL federation with "one graph" for backend services path now. Surely not a holy grail but way better than openAPI.
Sorry if that's not very clear, better read : https://ts-rest.com/docs/intro
Also, it integrates well with OpenAPI to document the API
I am using it between NestJs and SvelteKit and it's great
pathResponses:
notFound:
status: 404
contentType: application/http-problem
apiResponses:
serverError:
status: 5XX
contentType: application/http-problem
Can't stuff like this be implicit rather than needing to be defined? Or, to say it another way, we're building this over HTTP, which already defines these things and many others.Our TL;DR: the standard looks interesting, the developer tools need a lot of love. The whole world is using JSON/YAML these days, but developer experience for JSON Schema is lightyears behind "legacy" XSD
The ideal system, to me (speaking as the most prolific contributor to JSON Schema drafts-07 through 2020-12), would have clearly defined code generation and runtime validation features that did not get in each other's way. Keywords like "anyOf" and "not" are very useful for runtime validation but should be excluded from type definition / code generation semantics.
This would also help balance the needs of strongly typed languages vs dynamically typed languages.
The fundamental problem is that JSON Schema was designed as a constraint validation system (https://modern-json-schema.com/json-schema-is-a-constraint-s...) and it's been overloaded for type definition in ways that don't always make sense. But the JSON Schema alternatives that I've seen go too far in the other direction. There is a lot of value in being able to perform more complex runtime validation in a language-independent way.
There is a balance and clear separation of concerns needed between data definition and runtime validation, although they still should live in the same contract as there is considerable overlap. Now if I could only find someone who wants to fund the design of such a system... :-)
In the past, I wrapped io-ts with OpenAPI docgen logic. This worked great: ~every network boundary type was defined once, providing runtime parse/validation and serialization, inferred static types, and API documentation all at once.
Unfortunately I didn’t get permission to open source the solution. But if I ever get bandwidth to build its spiritual successor, I’ll tackle it from the opposite direction: the OpenAPI/JSON Schema specs are very well suited to define the underlying primitives for a zod/io-ts like solution. The great thing about inverting the direction is that it defers “generating” anything to the static type system, ie there’s much less surface area for the runtime and doc schemas to diverge.
It also provides opportunities to enrich static types with more information in a general way (eg “brand” a number with its valid range, or a string as a valid email address). This would allow the benefits of runtime checks at API boundaries to be treated as static checks internally without additional/redundant internal runtime overhead.
Extracts the JSONSchema based on path+method+response type from OpenAPI and then converts that to typescript
If the spec supports a field to specify the type for a component, that would help a lot with static type generation.
and no different in understandability.