Write OpenAPI with TypeSpec

bterlson | 118 points

As someone who has used JAX-RS (Java) and ASP.NET, APIs are basically created with these kinds of annotations right in the language.

    @GET
    public Character getCharacter(@PathParam("id") int id) {
        return db.getCharacter(id);
    }

That's very similar to TypeSpec's

    op getCharacter(@path id: safeint): Character;
Java and C# classes already have the type information that you'd be getting from a TypeSpec:

    // TypeSpec
    model Character {
      name: string;
      id: safeint;
      status: "Alive" | "Dead";
      class: Class;
    }

    enum Class { warrior; wizard; }

    // C#
    public class Character {
      public string Name { get; set; }
      public int Id { get; set; }
      public Status Status { get; set; }
      public CharacterClass CharacterClass { get; set; }
    }

    public enum Status { Alive, Dead }
    public enum CharacterClass { Warrior, Wizard }
But with C#/Java you don't need to write the spec and make sure that it stays in sync with what your endpoints are actually doing. TypeSpec looks a lot better than writing YAML by hand, but I'd still rather something that was tied to the actual code that will be executing so that things are never out of sync or wrong.
mdasen | a month ago

I like the idea, especially the TS-like syntax around enums and union types. I've always preferred the SDL for GraphQL vs writing OpenAPI for similar reasons. Most APIs I've run into in my career would benefit from modeling their API responses as ADTs versus the usual approach of overloading 4 different union members into a giant spare object.

I echo the sentiment others have brought up, which is the trade-offs of a code-driven schema vs schema-driven code.

At work we use Pydantic and FastAPI to generate the OpenAPI contract, but there's some cruft and care needed around exposing those underlying Pydantic models through the API documentation. It's been easy to create schemas that have compatibility problems when run through other code generators. I know there are projects such as connexction[1] which attempt to inverse this, but I don't have much experience with it. In the GraphQL space it seems that code-first approaches are becoming more favored, though there's a different level of complexity needed to create a "typesafe" GraphQL server (eg. model mismatches between root query resolvers and field resolvers).

[1] https://github.com/spec-first/connexion

Stoids | a month ago

We're also spec-first at my co (pretty mature, large API surface area), but maintaining the OpenAPI spec in YAML is extremely annoying and tedious. This checks a lot of boxes in terms of being interesting, but what does the path from an existing YAML spec to TypeSpec look like? In an ideal world we could just ingest our OpenAPI spec, get the TypeSpec version out, then be able to clean up/iterate from there.

I didn't see anything in the docs, but I would love for someone to tell me this exists.

Re: the post itself, this was much more compelling than TypeSpec's website imo. At the very least it feels like some of these examples, such as the enum bit, should be ported there. Also interesting that there isn't a single mention of languages like Cue[1].

[1] https://cuelang.org/

mmcclure | a month ago

Targeting OpenAPI 3.0 is a bad idea. It wasn't until 3.1 that it became truly useful, if only because it fixed one glaring blunder: Pre-3.1, you inexplicably couldn't provide a description along with a $ref. This defeats one of the major purposes of OpenAPI, which is... documenting your API.

Example: If you define a structure called Rectangle and use it all over the place, you can't say what this rectangle means or that rectangle means when the structure is used in your API. How was that ever deemed acceptable? Widespread use of common structures (which one would certainly expect in a well-designed API) resulted in an API that couldn't be documented in OAS.

The OpenAPI code-generation landscape is truly abysmal. Not only do almost none of the tools support version 3.1 (which has been current for years), but the generators themselves are of widely varying (but mostly shitty) quality and scattered across a couple of apparent major repos. The documentation is likewise a confusing morass of conflicting and incomplete information from several sources. I flailed away trying to fix one generator for weeks, or write a new one. But I couldn't even find a concise list of the data structures offered to the template engine after an OpenAPI doc is ingested by OpenAPIGenerator. Isn't that a fundamental part of creating a new generator?

So it raises the question of whether OpenAPI is salvageable, or whether this new DSL is better... from design to code generation. Given the trashy state of OpenAPI code generators, why bother generating OAS at all? Just move on and write some competent generators using this new language.

FourOnTheFloor | a month ago

Wow, I was _just_ thinking that it would be excellent to have something like this the other day.

I really prefer spec-first development, since it gives other developers an opportunity to review the API _before_ the changes start being implemented, and trying to do this from a codegen-based OpenAPI implementation frequently leads to the PR being "broken", since you've only changed the signature of all your methods and not the implementation yet.

But changing a big OpenAPI yaml file is really hard to easily review, and breaking it up into included files only helps a little, honestly. We can compile the new spec and host the HTML in a temporary location to make it easier to view what the new spec will look like, but once you've done that, you're no longer looking at the diff of what's changed.

TypeSpec looks terse enough to be easy to review (and to write!) which really looks like it'll help with that. I'll have to mess around with it in some personal projects of mine with reasonably-complicated specs and see if there's no obvious speedbumps first, but I'm hoping not, because I'd love to start using this in all the projects I'm contributing to!

DanHulton | a month ago

I wonder why this isn't written in Typescript directly so as to handle documentation, validation and type definitions in one go.

Especially if you want to reuse type definitions of objects elsewhere in your code.

An API request or response usually do not contain the exact model attributes. Only the public facing ones which are then often filtered down some more, depending on the user making the request (and their roles, groups, permissions).

LunaSea | a month ago

Question for the author, why not fully adopt TypeScript? Ie have TypeSpec be a well-defined, limited subset of TypeScript? At first glance, it seems to me that a model maps to an interface, an op is a function, and an enum is a string literal union.

I understand that you can’t express everything that way, eg annotations, but you could get pretty far with putting those in JSDoc comments, no? I could see a big practical benefit to TS code being able to read an API spec directly without another conversion step, so I’m curious why you chose not to go with that.

skrebbel | a month ago

What are people's thoughts on this vs AWS's Smithy?

We've been looking to buy into Smithy heavily as we no longer have the appetite to deal with OpenAPI and it's horrendous ecosystem.

lijok | a month ago

Ooh, magick.css ( https://css.winterveil.net/ ) spotted in the wild!

Discussion on magick.css from 5 days ago: https://news.ycombinator.com/item?id=39793513

btown | a month ago

I find it kind of discouraging that we now need a language to define an API that compiles into OpenAPI but honestly I hate writing OpenAPI specs (especially compared to gql).

So despite thinking it’s kinda dumb, Im all ears. Halfway through the article and it seems compelling so far.

nonethewiser | a month ago

Great idea - spend far too long reading & writing OpenAPI!

Particularly anyOf, allOf and oneOf (especially when nested) lead to really confusing nested specifications in OpenAPI. Really like how TypeSpec handles unions & intersections.

Playground is great for getting a feel for it fast too

henry_pulver | a month ago

This project seems really interesting, I'm surprised this is the first time I'm hearing about it. Like another commenter here I'm also surprised to see no mention of Cue (or now Apple's Pkl). Is this meant to be something similar to those?

Also given it's a MS project, would love for there to be some drop in NuGet package for C# use. All these data description languages are really interesting but the fact none of them have a C ABI or only work in the language of choice seems to limit how useful they can be outside of their original context.

kkukshtel | a month ago

I've been toying with the design of something similar here and there over the past couple of months. It never made it past the pen-and-paper stage though. Pleasantly surprised to find this today and will have to try it out for a work project.

My original motivation was the lack of OpenAPI definitions across projects, despite everyone agreeing their existence is a "best practice". As you suggest, developers just truly hate writing them. Unfortunately, even the "generate it from code" approach tends to be complicated and often feels duct-taped together.

ramilefu | a month ago

Looks interesting. Can it handle asyncapi specs too?

We use boats (npm) to define our openapi and asyncapi specs which uses file based structure to separate definitions for models, paths, params etc into separate files which makes it much more maintainable. Native refs make it super easy to reuse definitions, and you can write custom helpers in js to abstract things like the Page definition in that example.

Having a shared templating language for openapi / asyncapi specs which is ergonomic and can be used by more than just JS devs is a great idea. I'll keep an eye on this project.

buzziebee | a month ago

When I first saw TypeSpec, I was interested in it as a way to have a single-source-of-truth for both OpenAPI and GraphQL schemas... but unfortunately though the readme mentions GraphQL, the team at Microsoft didn't actually get around to adding support to output GraphQL schemas.

drewda | a month ago

How does TypeSpec compare with Bru[0], the DSL used by Bruno? Why would I choose one over the other?

[0] https://docs.usebruno.com/bru-lang-overview.html

dheerajvs | a month ago

TypeSpec is great, but if you're working with Rust and you're about to write a new project that will require an OpenApi spec sooner or later, I'd like to recommend a web framework that has spec generation baked in:

https://github.com/poem-web/poem (see poem_openapi)

All you need to do is derive a trait on your response structs and in return you get an almost perfectly generated spec. Unions, objects, enums are first class citizens.

Also, if you're from coming from PHP, the controllers feel very much like symfony controllers.

P.s. Please do recommend an ORM that would feel closer to doctrine. I miss doctrine.

zexodus | a month ago

Writing OpenAPI by hand sucks, this looks fantastic. The only reason I prefer generators from code comments is because the structure and the code itself live in the same place, so it's harder to forget.

dylmye | a month ago

Cool, it certainly feels like there's some unrealized better way to describe APIs -- but the part that's more interesting for me is the codegen story? Or some way to validate the service I implemented is actually conformant?

I guess for now it translates to OpenAPI, but the footnote implies that TypeSpec-driven codegen could be better. Maybe more is coming soon?

k8svet | a month ago

As someone who wrote a custom dsl to Open API spec script (in Ruby) I'm really interested to see if type spec would be a better output. Our spec is > 12k lines and we haven't even included everything that needs to be defined.

Does anyone have experience layering this on to an existing API (as opposed to defining the API and then building the code)?

mooreds | a month ago

This is cool! I made a similar project to this one when we switched from GraphQl to regular REST at $work but decided to drop it since I didn't have the bandwidth to work on it.

I see editor support for VSCode, but is it backed by an LSP or is it VSCode only?

emi2k01 | a month ago

I specified an existing api in this, and I think it turned out pretty good. It exports openapi and json-schema from the same spec. Looking forward to having more linters for specs and stuff like that.

lacrosse_tannin | a month ago

We have been using this for a couple of months now and it works really well. It's so much better than writing OpenAPI by hand, and I never ever will do that again after working with TypeSpec.

Thank you!

dajonker | a month ago

As someone who prefers schema-first design this looks awesome. Having to hand cut OpenAPI specs is gross and quickly becomes unwieldy.

tholm | a month ago

I found the blog's soft font hard to read. Fortunately my browser's reader mode fixed it.

esafak | a month ago

This looks great, is there a converter to convert an OpenAPI yml file to TypeSpec?

patrick-fitz | a month ago

I don’t see a way to specify the security scheme. Am I missing something?

dadadad100 | a month ago

This is typical Microsoft - overly complicated solution in search of a problem. Yaml and Json are beloved for a reason - they're simple and effective.

tyayers | a month ago