Using TypeScript with the GraphQL API
This article explains how to make the most out of Prepr GraphQL API features when using TypeScript in your front-end app.
Introduction
TypeScript is a strongly typed programming language that builds on JavaScript. Developers choose to use TypeScript instead of JavaScript in their projects to take advantage of cleaner and more scalable code, better code readability, type safety, and code that's easier to debug and test.
Use case
By using TypeScript in Strict Mode and a GraphQL code generator, a developer will get stronger guarantees of program correctness for types and null checks.
Prepr delivers strict types in the GraphQL API schema so that TypeScript in strict mode (opens in a new tab) can support the following situations in the front end:
- To process content fields in their expected data types.
- To make sure that some fields in content items are not null before rendering the content in the front end.
Use strict mode in Prepr to generate strong types
Follow the steps below to use the strict mode feature in Prepr CMS to process an API response in your front end with strong TypeScript types.
The steps below are based on a simple Next 13 project that connects to Prepr using the Apollo client for GraphQL API requests.
Step 1: Mark the fields that are required
Generally, one identifies required fields while modeling content. When it's time to set up the Schema in Prepr, configure these fields with the following steps:
- Click the Schema tab to open the Schema Editor and click the applicable model.
- Click the field that needs to be set as required, choose the Validation tab and enable the This field is required toggle.
Step 2: Set workflow stages to trigger the required validation
Content editors collaborate with each other by setting different workflow stages on content items such as In progress or Review before publishing them.
To make sure that content editors always fill the required fields at the correct workflow stage, go to the Settings → General screen and choose the Applicable workflow stage for required validation.
Once this step is done, it means that all required fields will not have null or undefined values for your API request. The steps below will ensure that the TypeScript in your front end will highlight code that does not process these fields accurately to ensure that they are not null or not undefined.
Step 3: Enable strict mode
To make sure to get accurate TypeScript types from your GraphQL schema, go to the Settings → Access Tokens screen. Choose the GraphQL access token with the same available workflow stages chosen above for the required validation and check the Enable strict mode box.
Now that the Prepr environment is ready, follow the steps below to update your front end.
Step 4: Add GraphQL queries to your front-end application
Add GraphQL queries to your front-end application like in the example below.
query Home{
Articles {
items {
_id
_slug
title
}
total
}
}
For more information on how to connect your front-end app to Prepr to fetch content, check out how to install the Apollo client for examples.
In the next steps, you will install and use a GraphQL code generator to generate the TypeScript types from your queries.
Step 5: Install a GraphQL code generator
Now that your queries are ready, install a GraphQL code generator like codegen (opens in a new tab) with the following command:
npm i @graphql-codegen/cli @graphql-codegen/typed-document-node @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-typed-document-node/core
Once successfully installed, create a configuration file, graphql.config.yml
in the root of your project like in the example below.
-
Set the
schema
value to the API URL from a Prepr access token. -
Set the
documents
value to the location of your queries. -
Set the
generates
path to the directory where the generated TypeScript file should be placed.
schema:
- https://graphql.prepr.io/aab2129476732aa4e5e1a2b93fb4e5...
documents: './queries/*.graphql'
generates:
generated/generated.ts:
plugins:
- typescript
- typescript-operations:
strictScalars: true
scalars:
_Date: string
_DateTime: string
- typed-document-node
Step 6: Generate TypeScript types
Generate the TypeScript types by adding the following line in the scripts
object of the package.json
file.
"generate-ts": "graphql-codegen --config graphql.config.yml",
and run the following command:
npm run generate-ts
The generated.ts
file will be generated like in the partial example below.
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
_Date: { input: any; output: any; }
_DateTime: { input: any; output: any; }
};
/** This union type holds all content models. */
export type AllModels = Article | Blogpost | CallToAction | Category | Page | Person;
export type ApplePodcast = {
__typename?: 'ApplePodcast';
_id: Scalars['String']['output'];
url: Scalars['String']['output'];
};
/** Single Article. */
export type Article = Model & {
__typename?: 'Article';
/** Count of bookmark events. */
_bookmarks: Scalars['Int']['output'];
/** The time the content item was changed. */
_changed_on: Scalars['String']['output'];
/** Count of clicktrough events. */
_clicktroughs: Scalars['Int']['output'];
/** Count of comment events. */
_comments: Scalars['Int']['output'];
_context?: Maybe<Context>;
/** The time the content item was created. */
_created_on: Scalars['String']['output'];
/** Id of your Prepr Environment. */
_environment_id: Scalars['String']['output'];
/** Unique identifier for each content item. */
_id: Scalars['String']['output'];
_last_published_on?: Maybe<Scalars['String']['output']>;
/** Count of like events. */
_likes: Scalars['Int']['output'];
_locale: Scalars['String']['output'];
_locales: Array<Scalars['String']['output']>;
/** This field returns all localizations for this content item. */
_localizations: Array<Article>;
/** The time for when the content item is or will be published. */
_publish_on?: Maybe<Scalars['String']['output']>;
/** Count of purchase events. */
_purchases: Scalars['Int']['output'];
/** Calculated time to read in minutes. */
_read_time?: Maybe<Scalars['Int']['output']>;
/** Count of share events. */
_shares: Scalars['Int']['output'];
/** Unique within Type, string identifier for each content item. */
_slug?: Maybe<Scalars['String']['output']>;
/** Count of subscribe events. */
_subscribes: Scalars['Int']['output'];
/** Count of view events. */
_views: Scalars['Int']['output'];
/** Count of vote events. */
_votes: Scalars['Int']['output'];
authors: Array<Person>;
border_color?: Maybe<Scalars['String']['output']>;
categories: Array<Category>;
content?: Maybe<Array<Maybe<_Prepr_Types>>>;
cooking_posts?: Maybe<TwitterPost>;
cover: Array<Asset>;
event_location?: Maybe<Coordinates>;
excerpt?: Maybe<Scalars['String']['output']>;
my_html_text?: Maybe<Scalars['String']['output']>;
my_text_area?: Maybe<Scalars['String']['output']>;
opening_times?: Maybe<BusinessHours>;
prepr_shop: Array<PreprExampleShop>;
search_keywords?: Maybe<Array<Maybe<Tag>>>;
seo?: Maybe<Seo>;
test_boolean?: Maybe<Scalars['Boolean']['output']>;
title: Scalars['String']['output'];
};
/** ArticleCollection component. */
export type ArticleCollection = Component & {
__typename?: 'ArticleCollection';
_context?: Maybe<Context>;
_id: Scalars['String']['output'];
articles: Array<Article>;
cta_label?: Maybe<Scalars['String']['output']>;
cta_url?: Maybe<Scalars['String']['output']>;
description?: Maybe<Scalars['String']['output']>;
heading?: Maybe<Scalars['String']['output']>;
};
...
export type HomeQueryVariables = Exact<{ [key: string]: never; }>;
export type HomeQuery = { __typename?: 'Query', Articles?: { __typename?: 'Articles', total: number, items: Array<{ __typename?: 'Article', _id: string, _slug?: string | null, title: string }> } | null };
export const HomeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Home"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"Articles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"_id"}},{"kind":"Field","name":{"kind":"Name","value":"_slug"}},{"kind":"Field","name":{"kind":"Name","value":"title"}}]}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]} as unknown as DocumentNode<HomeQuery, HomeQueryVariables>;
Note that a HomeDocument
is generated based on the query Home
defined in the previous step. This document will be used to make a query request in the next step.
Step 7: Make the GraphQL request
Once the types are generated and available you can make the GraphQL request to fetch the content you need to render in the front end.
Fetch the content for your front-end pages like in the example page.tsx
below.
import { Article, HomeDocument } from "../generated/generated"
import client from '@/services/apollo-client';
async function getData() {
// Make the query request using the generated HomeDocument
const {data} = await client.query({
query: HomeDocument,
})
// Return the list of articles from the query results
return data.Articles?.items;
}
export default async function Home() {
const articles = await getData();
return (
<ul>
{articles && articles.map((article) => (
<li key={article._id}>
{article.title}
</li>
))}
</ul>
)
}
Was this article helpful?
We’d love to learn from your feedback