- Issue created by @coby.sher
- πΊπΈUnited States brianperry
Also wonder if https://www.drupal.org/project/jsonapi_schema β and https://www.npmjs.com/package/json-schema-to-typescript could provide some options to solve this as well.
- π΅π±Poland marcin maruszewski
Is there a roadmap for this issue? I ask because I've been thinking for a while about creating some sort of example of Drupal's ability to use JSON:API and Typescript in NextJS, but I don't quite know how to combine it.
I've been looking at jsonapi_schema and json-schema-to-typescript, but I can see that at this stage these are not things that would make working with types easier. They generate weird field names, weird types and don't give you the ability to link entities to each other. Or at least I don't know how to do that.
I'll be watching with interest to see further progress on this topic.
- Status changed to Active
9 months ago 9:10pm 7 April 2024 - πΊπΈUnited States brianperry
Hi @Marcin Maruszewski!
There isn't a roadmap for this issue quite yet. However, I'm hoping that this and other TypeScript related improvements are one of our main areas of focus after we finish getting the word out about the 1.0 release.
Helpful to know you're interested in this feature and will be following along.
- π΅π±Poland marcin maruszewski
While searching for a solution, I came across the https://github.com/glideapps/quicktype library. It provides a CLI for generating TypeScript interfaces/types from JSON files.
I created a JSON file from /jsonapi/node/blog/resource/schema and ran
quicktype --no-combine-classes --nice-property-names -o types/blog.ts schema/blog.json
.As you can see, there are some wired interfaces like FluffyData or PurpleProperties. Also, despite the --no-combine-classes flag, some classes are combined into one, like DrupalInternalNid.
But there are some advantages like Convert class witch to my understanding acts like a parser.
Is this knowledge helpful for this topic?
Hey Marcin. Yes I think that is helpful research, thank you!
I'm interested in looking into Zod too. Something like https://github.com/rsinohara/json-to-zod looks promising.
- πΊπΈUnited States brianperry
Thanks @marcin-maruszewski - any and all research is helpful at this point.
And +1 to taking a closer look at Zod. I'm currently working on a decoupled Drupal project that uses Zod extensively. It works well, but I believe the schemas were defined manually (that work was done before I was involved). Will be interested to see if something like https://github.com/rsinohara/json-to-zod could automate that, and how far it can scale. This project has many deeply nested Paragraphs for example.
- π΅π±Poland marcin maruszewski
FYI, I have created this GitHub page - https://isobar-playground.github.io/jsonapi/
It contains all the /schema routes generated with https://www.drupal.org/project/jsonapi_schema β for a fresh Drupal installation with a Blog content type with field_hero_image media reference. It may be helpful in explaining what Drupal can produce in terms of JSON:API schema files. Feel free to use it.
But TBH I think there is some confusion in creating schema with jsonapi_schema module, and output from /jsonapi pages. I used https://www.jsonschemavalidator.net/s/qpgAL5KB to validate Drupal node against generated schema and it failed. But if I just pass JSON from data then it also fails, but with some "normal" issues like missing log message or invalid type for timestamp fields. Is this how the JSON:API works?
- π΅π±Poland marcin maruszewski
I also found this interesting library - https://github.com/maasglobal/io-ts-from-json-schema. But it seems that when I try to generate type from https://isobar-playground.github.io/jsonapi/action/action/resource/schema/ it fails with the following error:
Error: properties keyword is not supported outside explicit object definitions. See https://github.com/maasglobal/io-ts-from-json-schema/issues/33
To be honest, I'm quite sad that (according to https://dri.es/headless-cms-rest-vs-jsonapi-vs-graphql) Drupal maintainers chose JSON:API as the main headless solution and didn't check if there was a solution for this problem for such a long time (the post is from more than 5 years ago). It looks like "on paper" JSON:API looks great (and I agree), but there are no ready-made tools for PHP programmers to jump into TypeScript and get all the benefits of it, like the discussed codegen for types.
But I also understand and appreciate the work of the people behind the API Client initiative and JSON:API. I just hope that one day there will be an out-of-the-box solution to this problem.
- πΊπΈUnited States brianperry
I've done some experimentation here and think this is achievable. The possible solution is quite different from what I expected though.
I came to the same conclusion that using json-schema isn't currently viable. I'm light on some of the details here because I did some of this a few weeks back and then had to set it aside, but there were two main issues.
* Using the jsonapi_schema module I found some of the results for resources unstable. I unfortunately can't remember the specifics, but some key resources we'd need were returning 500s. Will try to reproduce this at some point and create an issue in the jsonapi_schema project.
* Even if that was stable, I found that packages like https://www.npmjs.com/package/json-schema-to-zod would choke on schemas with relationships due to circular references. It kind of makes sense given the JSON:API spec, but it is extremely problematic for this use case.So based on that, I don't see a path forward with json-schema.
On the other hand, I found that json-to-zod works really well.
I created a small POC here: https://github.com/backlineint/schema-gen-poc
It actually just uses json-to-zod with the standard JSON:API endpoints to generate then schemas we need. Even more amusingly it actually uses our client to feed json into json-to-zod. Here is the entire POC script:
import * as fs from "fs"; import { JsonApiClient } from "@drupal-api-client/json-api-client"; import { jsonToZod } from "json-to-zod"; const client = new JsonApiClient( "https://dev-drupal-api-client-poc.pantheonsite.io" ); const zodImport = "import { z } from 'astro:content';\n\nexport "; const writeSchema = (schema, fileName) => { fs.writeFile(fileName, zodImport, { flag: "w" }, (err) => { if (err) { console.error(err); } else { fs.appendFile(fileName, schema, (err) => { if (err) { console.error(err); } else { // file written successfully } }); } }); }; console.log("Generating schemas..."); // Generate a schema for an article collection. const articles = await client.getCollection("node--article", { queryString: "include=field_media_image.field_media_image", }); const articlesSchema = jsonToZod(articles).replace( "const schema", "const articlesSchema" ); writeSchema(articlesSchema, "src/lib/schemas/articlesSchema.ts"); // Generate a schema for a media--image resource const mediaImage = await client.getCollection("media--image"); const mediaImageSchema = jsonToZod(mediaImage.data[0]).replace( "const schema", "const mediaImageSchema" ); writeSchema(mediaImageSchema, "src/lib/schemas/mediaImageSchema.ts"); // Generate a schema for a file resource const file = await client.getCollection("file--file"); const fileSchema = jsonToZod(file.data[0]).replace( "const schema", "const fileSchema" ); writeSchema(fileSchema, "src/lib/schemas/fileSchema.ts");
The intent is to run that in advance and commit the schemas to source.
The experience of using those Zod schemas in code is really nice. This POC renders a list of articles at /blog. When sourcing article data, you'd do this:
import { articlesSchema } from "../../lib/schemas/articlesSchema"; type Articles = z.infer<typeof articlesSchema>; const articles = articlesSchema.parse(await client.getCollection<Articles>("node--article", { queryString: "include=field_media_image.field_media_image", }));
Zod will throw an error when parsing if something violates the schema. You also get full intellisense/autosuggest when working with articles on the template.
So I think this could work. What I don't know at the moment is how we could make this into a more generic utility for users. Open to any ideas. Or feedback on this approach in general.
I've done some experimentation here[...]
Looks great! I would love to turn this into some sort of CLI util that eventually could be run as a step in a `create-drupal-client-app` style starter, but given the fact that the client needs to be set up first I like the idea of this being pure documentation (with examples of course) for the first iteration.