Skip to content

Commit

Permalink
Merge pull request #2266 from strapi/enhancement/typescript-guides
Browse files Browse the repository at this point in the history
Add API Reference and Guides for TypeScript in docs
  • Loading branch information
meganelacheny authored Nov 6, 2024
2 parents 54c3f91 + 38faf6d commit 90230a4
Show file tree
Hide file tree
Showing 6 changed files with 528 additions and 250 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
49 changes: 25 additions & 24 deletions docusaurus/docs/dev-docs/typescript/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@
title: TypeScript development
description: Learn more about TypeScript usage with Strapi 5
tags:
- strapi() factory
- strapi.compile() function
- typescript
- plugins development

- strapi() factory
- strapi.compile() function
- typescript
- plugins development
- api
- guides
---

import NotV5 from '/docs/snippets/_not-updated-to-v5.md'
# TypeScript Development with Strapi

This page provides a comprehensive guide to developing with TypeScript in Strapi v5, covering key patterns and configurations for integrating TypeScript effectively. You'll find sections on using Strapi’s provided types, generating and managing typings for content types, configuring Strapi programmatically, and building plugins with TypeScript. Each section includes practical steps and example code to help you set up and troubleshoot TypeScript-based workflows in your Strapi project.

# TypeScript Development with Strapi
:::strapi API reference
We are preparing an API reference page, which will include an exhaustive list of the types exported by Strapi. Please come back soon! 👀
:::

<NotV5/>
## :books: Guides

While developing a [TypeScript](/dev-docs/typescript)-based application with Strapi, you can:
The guides are a curated list of common examples in which you might need to use Strapi types in your application.

- access [typings for the `Strapi`](#use-strapi-typescript-typings) class with autocompletion,
- [generate typings](#generate-typings-for-content-types-schemas) for your project's content-types,
- [start Strapi programmatically](#start-strapi-programmatically),
- and follow some TypeScript-specific instructions for [plugins development](#develop-a-plugin-using-typescript).
You can find the list of available guides [here](/dev-docs/typescript/development/guides).

## Use `Strapi` TypeScript typings

Expand All @@ -31,15 +33,15 @@ To experience TypeScript-based autocomplete while developing Strapi applications
1. Open the `./src/index.ts` file from your code editor.
2. Declare the `strapi` argument as type `Strapi` within the global `register` method:

```typescript title="./src/index.ts"
import { Strapi } from '@strapi/strapi';
```typescript title="./src/index.ts"
import type { Core } from '@strapi/strapi';

export default {
register({ strapi }: { strapi: Strapi }) {
// ...
},
};
```
export default {
register({ strapi }: { strapi: Core.Strapi }) {
// ...
},
};
```

3. Within the body of the `register` method, start typing `strapi.` and use keyboard arrows to browse the available properties.

Expand Down Expand Up @@ -116,10 +118,9 @@ To start Strapi programmatically in a TypeScript project the Strapi instance req
Strapi can be run programmatically by using the `strapi.createStrapi()` factory. Since the code of TypeScript projects is compiled in a specific directory, the parameter `distDir` should be passed to the factory to indicate where the compiled code should be read:

```js title="./server.js"
const strapi = require('@strapi/strapi');
const app = strapi.createStrapi({ distDir: './dist' });
app.start();
app.start();
```

### Use the `strapi.compile()` function
Expand All @@ -129,7 +130,7 @@ The `strapi.compile()` function should be mostly used for developing tools that
```js
const strapi = require('@strapi/strapi');

strapi.compile().then(appContext => strapi(appContext).start());
strapi.compile().then((appContext) => strapi(appContext).start());
```

## Develop a plugin using TypeScript
Expand Down
12 changes: 12 additions & 0 deletions docusaurus/docs/dev-docs/typescript/development/api-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: TypeScript - API Reference
sidebar_label: API Reference
description: API Reference for Strapi Type System
tags:
- typescript
- api
---

# API Reference

:building_construction: **Still under construction, come back later** :construction:
18 changes: 18 additions & 0 deletions docusaurus/docs/dev-docs/typescript/development/guides.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: TypeScript - Guides
sidebar_label: Guides
description: List of TypeScript guides to get started with Strapi and TypeScript
tags:
- typescript
- guides
---

# TypeScript Guides

This page includes a curated list of common examples useful to use types while [developing a Strapi TypeScript application](/dev-docs/typescript/development).

<CustomDocCardsWrapper>

<CustomDocCard emoji="📄" title="Manipulating Data" description="Manipulate documents and components in a type-safe manner." link="/dev-docs/typescript/development/guides/documents-and-entries"/>

</CustomDocCardsWrapper>
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
---
title: TypeScript - Manipulating Documents and Entries
sidebar_label: Manipulating Documents and Entries
description: TypeScript guide to get started with manipulating documents and entries
tags:
- typescript
- guides
- data
- document
- component
- uid
- entries
---

# Manipulating documents and entries

This guide will explore TypeScript patterns for manipulating documents and entries in a Strapi v5 application, including how to leverage Strapi's `UID` and `Data` namespaces to interact with both generic and known entity types safely. If you're working on a TypeScript-based Strapi project, mastering these approaches will help you take full advantage of type safety and code completion, ensuring robust, error-free interactions with your application’s content and components.

:::prerequisites

- **Strapi Application:** A Strapi v5 application. If you don't have one, follow the [documentation](/dev-docs/quick-start) to get started.
- **TypeScript:** Ensure TypeScript is set up in your Strapi project. You can follow Strapi's [official guide](/dev-docs/typescript#getting-started-with-typescript-in-strapi) on configuring TypeScript.
- **Generated Types:** Application types [have been generated](/dev-docs/typescript/development#generate-typings-for-content-types-schemas) and are accessible.
:::

## Type Imports

The `UID` namespace contains literal unions representing the available resources in the application.

```typescript
import type { UID } from '@strapi/strapi';
```

- `UID.ContentType` represents a union of every content-type identifier in the application
- `UID.Component` represents a union of every component identifier in the application
- `UID.Schema` represents a union of every schema (content-type or component) identifier in the application
- And others...

---

Strapi provides a `Data` namespace containing several built-in types for entity representation.

```typescript
import type { Data } from '@strapi/strapi';
```

- `Data.ContentType` represents a Strapi document object
- `Data.Component` represents a Strapi component object
- `Data.Entity` represents either a document or a component

:::tip
Both the entities' type definitions and UIDs are based on the generated schema types for your application.

In case of a mismatch or error, you can always [regenerate the types](/dev-docs/typescript/development#generate-typings-for-content-types-schemas).
:::

## Usage

### Generic entities

When dealing with generic data, it is recommended to use non-parametrized forms of the `Data` types.

#### Generic documents

```typescript
async function save(name: string, document: Data.ContentType) {
await writeCSV(name, document);
// ^ {
// id: Data.ID;
// documentId: string;
// createdAt?: DateTimeValue;
// updatedAt?: DateTimeValue;
// publishedAt?: DateTimeValue;
// ...
// }
}
```

:::warning
In the preceding example, the resolved properties for `document` are those common to every content-type.

Other properties have to be checked manually using type guards.

```typescript
if ('my_prop' in document) {
return document.my_prop;
}
```

:::

#### Generic components

```typescript
function renderComponent(parent: Node, component: Data.Component) {
const elements: Element[] = [];
const properties = Object.entries(component);

for (const [name, value] of properties) {
// ^ ^
// string any
const paragraph = document.createElement('p');

paragraph.textContent = `Key: ${name}, Value: ${value}`;

elements.push(paragraph);
}

parent.append(...elements);
}
```

### Known entities

When manipulating known entities, it is possible to parametrize `Data` types for better type safety and code completion.

#### Known documents

```typescript
const ALL_CATEGORIES = ['food', 'tech', 'travel'];

function validateArticle(article: Data.ContentType<'api::article.article'>) {
const { title, category } = article;
// ^? ^?
// string Data.ContentType<'api::category.category'>

if (title.length < 5) {
throw new Error('Title too short');
}

if (!ALL_CATEGORIES.includes(category.name)) {
throw new Error(`Unknown category ${category.name}`);
}
}
```

#### Known components

```typescript
function processUsageMetrics(
id: string,
metrics: Data.Component<'app.metrics'>
) {
telemetry.send(id, { clicks: metrics.clicks, views: metrics.views });
}
```

### Advanced use cases

#### Entities subsets

Using the types' second parameter (`TKeys`), it is possible to obtain a subset of an entity.

```typescript
type Credentials = Data.ContentType<'api::acount.acount', 'email' | 'password'>;
// ^? { email: string; password: string }
```

```typescript
type UsageMetrics = Data.Component<'app.metrics', 'clicks' | 'views'>;
// ^? { clicks: number; views: number }
```

#### Type argument inference

It is possible to bind and restrict an entity type based on other function parameters.

In the following example, the `uid` type is inferred upon usage as `T` and used as a type parameter for the `document`.

```typescript
import type { UID } from '@strapi/strapi';

function display<T extends UID.ContentType>(
uid: T,
document: Data.ContentType<T>
) {
switch (uid) {
case 'api::article.article': {
return document.title;
// ^? string
// ^? Data.ContentType<'api::article.article'>
}
case 'api::category.category': {
return document.name;
// ^? string
// ^? Data.ContentType<'api::category.category'>
}
case 'api::account.account': {
return document.email;
// ^? string
// ^? Data.ContentType<'api::account.account'>
}
default: {
throw new Error(`unknown content-type uid: "${uid}"`);
}
}
}
```

When calling the function, the `document` type needs to match the given `uid`.

```typescript
declare const article: Data.Document<'api::article.article'>;
declare const category: Data.Document<'api::category.category'>;
declare const account: Data.Document<'api::account.account'>;

display('api::article.article', article);
display('api::category.category', category);
display('api::account.account', account);
// ^ ✅

display('api::article.article', category);
// ^ Error: "category" is not assignable to parameter of type ContentType<'api::article.article'>
```
Loading

0 comments on commit 90230a4

Please sign in to comment.