- Published on
How to use Openapi-typescript module to generate typescript types for Strapi integration
I spend most of my time with strongly typed languages like Go and Dart. I've learned to stop worrying and love the compiler. Honestly, i wish someone had said something earlier in my career. My initial experiences with Java in the late 90's put me right off compiled languages. This error led to wandering the PHP wilderness for ten years.
More recently I've been working on Video Content Management at scale using Kubernetes on GCP and AWS, and trying to find something as useful as Drupal was back in the day. I hear it's still useful but it's still on PHP... and the community is still... the community. So i'm not touching that with anyone's barge pole.
I have however invested a lot of time into learning Strapi - an extensible headless content management system written in Typescript. Ok, so still not compiled to distributable binaries, but at least it's a fully fledged type system. Strapi is pretty awesome. It does a lot of stuff really well, and implements the Dependency Injection,Strategy and Middleware patterns atop an MVC architecture and an effective Plugin API, making adding or altering functionality a breeze. It's a real pleasure to work with. An important extension is the Documentation - this will walk your routes and types and produce valid OpenAPI 3.0 documentation which you can browse and test using the HTML frontend. All good stuff.
The special sauce though is that this is a standardised representation of your entire application. From what i've read you can use it to run fully typed fetch methods and the like. Sounds fantastic.
For now though i wanted to integrate a NextJS 13 app with Strapi and I wanted it as strongly typed as possible. This next section discusses how I've used 'OpenAPI-typescript' to generate the types and then integrate them with NextJS getStaticProps and getStaticPaths to speed development and fully utilise all the great stuff that's availble in an IDE when you have types available.
Here's how I've gone about it. I'm sure it can be improved though.
Generate Strapi OpenAPI documentation
Install the Documentation plugin in Strapi.
npx strapi install documentation
When this is running correctly you will have a json file that fully describes the API. This will be located here: myStrapiApp/src/extensions/documentation/documentation/1.0.0/full_documentation.json
Generate typscript types
Install openapi-typescript from npmjs.com or Github.
npm i -g openapi-typescript
Then run the tool against the OpenAPI documentation you just created:
npx openapi-typescript myStrapiApp/src/extensions/documentation/documentation/1.0.0/full_documentation.json --output src/types/schema.ts```
In this instance i am using the generated types as part of a Nextjs application. So i have set the output flag to dump the schema.ts file into the src/types
directory inside the Nextjs app.
Now i can import the types from Strapi and use them in my Nextjs app. In this example i'm using the types from the schema.ts
that was generated earlier. Another function has called the Strapi endpoint, and now this component will deal with the objects that were returned from Strapi. In this case the components["schemas"]["ArticleListResponseDataItem"]
is a News article and i've built the Nextjs app (where this is running) to specifically implement the Strapi API so i can write less code.
import React from "react";
import ArticleCard from "./ArticleCard";
import { components } from "../types/schema";
type Article = components["schemas"]["ArticleListResponseDataItem"];
interface NewsProps {
articles?: Article[];
children?: React.ReactNode;
}
Now i can destructure the data, using the types and i can then use them in the component:
const News: React.FC<NewsProps> = ({ articles = [], children }) => {
return (
<div className="container mx-auto px-6 pb-10">
<div className="mt-8 grid grid-cols-1 gap-16 md:mt-8 md:grid-cols-3">
{articles.map((article, index) => {
const id = article.id;
// there might be a better way to do this, but it was the only way i could stop the
// compiler complaining about nullable values
const { Title, updatedAt } = article.attributes ?? {};
const heroMedia = article.attributes?.heroMedia ?? {};
const heroMediaAttributes = heroMedia.data?.attributes ?? {};
const { url, height, width, alternativeText } = heroMediaAttributes;
if (Title && url) {
return (
<ArticleCard
key={index}
id={id}
attributes={{
Title,
updatedAt,
heroMedia: {
data: {
attributes: {
url,
height,
width,
alternativeText,
},
},
},
}}
/>
);
}
})}
{children}
</div>
</div>
);
};
The best thing though, is once you make a change to the content types in Strapi, you can trigger this to run again, refresh the schema and then you have the new field available. e.g. if i add a 'Slug' to the Article content type in Strapi, re run the openapi-typescript types generator and then make this change in the code:
const News: React.FC<NewsProps> = ({ articles = [], children }) => {
// ....snip...
// notice i've added Slug to the destructure from article.attributes
const { Title, updatedAt,Slug } = article.attributes ?? {};
const heroMedia = article.attributes?.heroMedia ?? {};
const heroMediaAttributes = heroMedia.data?.attributes ?? {};
const { url, height, width, alternativeText } = heroMediaAttributes;
if (Title && url) {
// And Slug is now passed to the ArticleCard as a prop
// Obviously you will need to alter the ArticleCard to work with the new prop
return (
<ArticleCard
key={index}
id={id}
attributes={{
Title,
Slug,
updatedAt,
heroMedia: {
// ....snip...
Then the Slug field i just added in Strapi is now available in the app as a fully typed variable.