stanriders/circleforms

Improve TS codegen

Closed this issue · 2 comments

B0und commented

Currently we are only generating types from swagger schema, but it`s possible to generate full api client as well. With the current workflow, after the backend changed some of the api types it proved to be quite difficult to adjust to all of the changes.

The new workflow of interacting with api would ideally look something like this:
[requirement 1]

// we would also get autocomplete on all possible backend actions with type safety
apiClient.postsPOST(myjson)

[requirement 2]
Another important requirement is to be able to set credentials: include (and custom headers in general) on the fetch api call. Because otherwise the auth cookie is not sent and the request fails with CORS errors. (shivers)

openapi-typescript

Currently we are using openapi-typescript package, which only generates types, but recommends to use openapi-typescript-fetch for generating the client.

openapi-typescript-fetch supports custom headers config, but the api client workflow looks like this:

// declare fetcher for paths
const fetcher = Fetcher.for<paths>();

// create fetch operations
export const apiClient = {
  postsPOST: fetcher.path("/posts").method("post").create(),
  postsIdGET: fetcher.path("/posts/{id}").method("get").create()
};

So we end up creating every single method by hand, which in my opinion is too much of an overhead.

Other options

Upon further research i stumbled upon this great article. Some of the best scoring generators in their opinion are Nswag and Typewriter.

Typewriter

So first things first, Typewriter while scoring extremely high, requires modifying c# backend code, which sounds quite questionable. I wasn`t really able to test this solution. No comments here.

Nswag

Pros:

  • Well maintained (5k stars)
  • One of the few tools which can not only generate api client/types from swagger.json, but from c# code itself! (Could be used to generate types on backend build)
  • Cool thing is that they actually have a GUI for this tool. There are like a dozen options on how you want to generate your TS code.

Cons:

  • I briefly tested the code that it generates and I actually wasnt able to figure out how to modify the request options easily
  • Docs while massive are not the best imo
  • Generates one huge 3k LOC file.
  • Code quality is kinda meh.

openapi-typescript-codegen

I stumbled upon this thing myself. Its relatively small (only ~700 stars), but i was pleasantly surprised!

Pros:

  • Very easy to customize the headers/options
  • Docs are nice. I was able to get up and running in no time. (In fact default config options already include credentials: "include" option!)
  • Generated code is nicely split and looks clean

Cons:

  • Small user base

I was ready to end my searches here, as it seemed like a suitable solution, but i ended up not noticing an elephant in the room.

Openapi Generator

This one is something else. Really massive (12k stars). Supports almost every language in existance. But lets focus on typescript. First of all it generates a bunch of files, with good structure: models & services. Here comes the mind blowing part. So our original type generator uses strings for dates, and while that technically correct (since that is what the api response returns), we would have to convert dates to strings manually everytime. Openapi Generator actually includes conversion code. So you need to pass a Date object into the PostsService.postPosts({... , activeTo: new Date()}) it will then convert it to iso string internally! Also out of the box it allows us to use camelCase for client-side state and it automatically transforms it into snake_case for backend to consume.
Have i mentioned that the code quality is just next level? To be completely honest i didnt even think about code quality much before looking at this (since its all just generated stuff, who cares), but this is just incredible. It meets both of the requirements and imo it looks like one of the best solutions we could use, but i`d love to hear your thoughts as well!

B0und commented

Upon further research it looks like this approach is not really compatible with SWR. Since it expects url as a parameter, and by using api client we have effectively abstracted it. However it looks like we could use React Query instead, which provides similar functionality without any issues.

SWR:

const { data, isValidating } = useSWR<PostsPage>(`/posts/page/1?pageSize=4&filter=Active`, api)

React Query:

 const { isLoading, error, data } = useQuery(["posts", 1], () =>
    apiClient.pages.postsPagePageGet({ page: 1, filter: PostFilter.Active, pageSize: 4 })
  );

One additional bonus, is that all of the parameters are typesafe and you cannot accidentally make a typo e.g filter=Activ instead of Active.

I'm totally okay with openapi generator + react query! It sounds a lot better than just generating types and import them everywhere as well