Skip to content

feat(content-types): allow custom root paths for CT managers #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
- [API Token Authentication](#api-token-authentication)
3. [API Reference](#-api-reference)
4. [Resource Managers](#-resource-managers)
- [`.collection()`](#collectionresource)
- [`.single()`](#singleresource)
- [`.collection()`](#collectionresource-options)
- [`.single()`](#singleresource-options)
- [`.files`](#files)
5. [Debug](#-debug)
6. [Demo Projects](#-demo-projects)
Expand Down Expand Up @@ -123,18 +123,23 @@ const client = strapi({
The Strapi client library instance provides key properties and utility methods for content and API interaction:

- **`baseURL`**: base URL of your Strapi backend.
- **`fetch`**: perform generic requests to the Strapi Content API using fetch-like syntax.
- **`.collection(resource: string)`**: get a manager instance for handling collection-type resources.
- **`.single(resource: string)`**: get a manager instance for handling single-type resources.
- **`fetch()`**: perform generic requests to the Strapi Content API using fetch-like syntax.
- **`collection()`**: get a manager instance for handling collection-type resources.
- **`single()`**: get a manager instance for handling single-type resources.
- **`files`**: access the files manager instance for handling common files operations.

## 📁 Resource Managers

### `.collection(resource)`
### `.collection(resource, [options])`

The `.collection()` method provides a manager for working with collection-type resources,
which can have multiple entries.

**Note**: the `resource` corresponds to the plural name of your collection type, as defined in the Strapi model.
#### Params

- `resource`: `string` - plural name of your collection type, as defined in the Strapi model
- `[options]`: `object` - additional options to pass to the collection type manager
- `[path]`: `string` - optional root path override for the manager's queries

#### Available Methods:

Expand Down Expand Up @@ -168,11 +173,21 @@ const updatedArticle = await articles.update('article-document-id', { title: 'Up
await articles.delete('article-id');
```

### `.single(resource)`
You can also customize the root path for requests by providing a value for the `path` option:

```typescript
const articles = client.collection('articles', { path: '/my-custom-path' });
```

### `.single(resource, [options])`

The `.single()` method provides a manager for working with single-type resources, which have only one entry.

**Note**: the `resource` corresponds to the singular name of your collection type, as defined in the Strapi model.
#### Params

- `resource`: `string` - singular name of your single type, as defined in the Strapi model
- `[options]`: `object` - additional options to pass to the single type manager
- `[path]`: `string` - optional root path override for the manager's queries

#### Available Methods:

Expand Down Expand Up @@ -201,9 +216,15 @@ const updatedHomepage = await homepage.update(
await homepage.delete();
```

### .files
You can also customize the root path for requests by providing a value for the `path` option:

The `files` property provides access to the Strapi Media Library through the Upload plugin. It allows you to retrieve files metadata without directly interacting with the REST API.
```typescript
const homepage = client.single('homepage', { path: '/my-custom-path' });
```

### `.files`

The `files` property provides access to the Strapi Media Library through the Upload plugin. It allows you to retrieve files metadata without directly interacting with the REST API manually.

#### Methods

Expand All @@ -212,7 +233,9 @@ The `files` property provides access to the Strapi Media Library through the Upl
- `update(fileId: number, fileInfo: FileUpdateData): Promise<FileResponse>` - Updates metadata for an existing file
- `delete(fileId: number): Promise<void>` - Deletes a file by its ID

#### Example: Finding Files
#### Examples

**Finding all files**

```typescript
// Initialize the client
Expand All @@ -235,7 +258,7 @@ const imageFiles = await client.files.find({
});
```

#### Example: Finding a Single File
**Finding a Single File**

```typescript
// Initialize the client
Expand All @@ -252,7 +275,7 @@ console.log(file.url); // The file URL
console.log(file.mime); // The file MIME type
```

#### Example: Updating File Metadata
**Updating File Metadata**

```typescript
// Initialize the client
Expand All @@ -272,7 +295,7 @@ console.log(updatedFile.name); // Updated file name
console.log(updatedFile.alternativeText); // Updated alt text
```

#### Example: Deleting a File
**Deleting a File**

```typescript
// Initialize the client
Expand Down Expand Up @@ -353,6 +376,7 @@ This repository includes demo projects located in the `/demo` directory to help
- **`demo/node-typescript`**: a Node.js project using TypeScript.
- **`demo/node-javascript`**: a Node.js project using JavaScript.
- **`demo/next-server-components`**: a Next.js project using TypeScript and server components.
- **`demo/react-vite`**: a React project using Vite and TypeScript

### Using Demo Commands

Expand Down
1 change: 1 addition & 0 deletions demo/react-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"framer-motion": "^12.6.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hot-toast": "^2.5.2",
"react-router-dom": "^7.5.0",
"tailwindcss": "^4.1.3"
},
Expand Down
26 changes: 26 additions & 0 deletions demo/react-vite/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 14 additions & 10 deletions demo/react-vite/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';

import { Home } from '@/pages/Home.tsx';
import { CollectionDemo } from '@/pages/CollectionDemo.tsx';
import { FilesDemo } from '@/pages/FilesDemo.tsx';

import { Home } from '@/pages/Home.tsx';
import { Toaster } from 'react-hot-toast';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';

export default function App() {
return (
<Router>
<Routes>
<Route path="/demos/collections" element={<CollectionDemo />} />
<Route path="/demos/files" element={<FilesDemo />} />
<Route path="/" element={<Home />} />
</Routes>
</Router>
<>
<Router>
<Routes>
<Route path="/demos/collections" element={<CollectionDemo />} />
<Route path="/demos/files" element={<FilesDemo />} />
<Route path="/" element={<Home />} />
</Routes>
</Router>
<Toaster position="bottom-right" reverseOrder={false} />
</>
);
}
4 changes: 3 additions & 1 deletion demo/react-vite/src/hooks/useFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useStrapi } from '@/hooks/useStrapi.ts';

import type { File } from '@/types.ts';
import React from 'react';
import toast from 'react-hot-toast';

export const useFiles = () => {
const strapi = useStrapi();
Expand All @@ -11,8 +12,9 @@ export const useFiles = () => {
try {
const response = await strapi.files.find();
setFiles(response);
toast.success(`${response.length} files fetched successfully`);
} catch (error) {
console.error('Error fetching files:', error);
toast.error(error instanceof Error ? error.message : `${error}`);
}
};

Expand Down
4 changes: 3 additions & 1 deletion demo/react-vite/src/pages/CollectionDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Layout } from '@/layouts/Layout.tsx';
import type { Category, QueryParam } from '@/types.ts';
import { DEFAULT_COLLECTION_QUERIES } from '@/utils/constants.ts';
import React, { useEffect, useState } from 'react';
import toast from 'react-hot-toast';

export const CollectionDemo: React.FC = () => {
const categories = useCollection('categories');
Expand All @@ -31,8 +32,9 @@ export const CollectionDemo: React.FC = () => {
try {
const { data } = await categories.find(query);
setDocuments(data as unknown as Category[]);
toast.success(`${data.length} categories fetched successfully`);
} catch (error) {
console.error('Error fetching categories:', error);
toast.error(error instanceof Error ? error.message : `${error}`);
}
};

Expand Down
37 changes: 28 additions & 9 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { HttpClient } from './http';
import { AuthInterceptors, HttpInterceptors } from './interceptors';
import { StrapiConfigValidator } from './validators';

import type { ContentTypeManagerOptions } from './content-types/abstract';
import type { HttpClientConfig } from './http';

const debug = createDebug('strapi:core');
Expand Down Expand Up @@ -303,18 +304,19 @@ export class StrapiClient {
*
* This instance provides methods for performing operations on the associated documents: create, read, update, delete.
*
* @param resource - The plural name of the collection to interact with.
* This should match the collection name as defined in the Strapi app.
* @param resource - The plural name of the collection to interact with.
* This should match the collection name as defined in the Strapi app.
* @param [options] - Optional parameter to specify additional configuration such as custom API path.
*
* @returns An instance of {@link CollectionTypeManager} targeting the given {@link resource} name.
* @returns An instance of {@link CollectionTypeManager} for the given {@link resource}.
*
* @example
* ```typescript
* // Initialize the client with required configuration
* const config = { baseURL: 'http://localhost:1337/api' };
* const client = new Strapi(config);
*
* // Retrieve a CollectionTypeManager for the 'articles' resource
* // Retrieve a CollectionTypeManager for the 'articles' collection
* const articles = client.collection('articles');
*
* // Example: find all articles
Expand All @@ -331,13 +333,19 @@ export class StrapiClient {
*
* // Example: delete an article
* await articles.delete('dde61ffb-00a6-4cc7-a61f-fb00a63cc740');
*
* // Example with a custom API path
* const customArticles = client.collection('articles', { path: '/custom-articles-path' });
* const customAllArticles = await customArticles.find();
* ```
*
* @see CollectionTypeManager
* @see StrapiClient
*/
collection(resource: string) {
return new CollectionTypeManager(resource, this._httpClient);
collection(resource: string, options: ClientCollectionOptions = {}) {
const { path } = options;

return new CollectionTypeManager({ resource, path }, this._httpClient);
}

/**
Expand All @@ -347,8 +355,9 @@ export class StrapiClient {
*
* @param resource - The singular name of the single-type resource to interact with.
* This should match the single-type name as defined in the Strapi app.
* @param [options] - Optional parameter to specify additional configuration such as custom API path.
*
* @returns An instance of {@link SingleTypeManager} targeting the given {@link resource} name.
* @returns An instance of {@link SingleTypeManager} for the given {@link resource}.
*
* @example
* ```typescript
Expand All @@ -366,12 +375,22 @@ export class StrapiClient {
*
* // Example: delete the homepage content
* await homepage.delete();
*
* // Example with a custom API path
* const customHomepage = client.single('homepage', { path: '/custom-homepage-path' });
* const customHomepageDocument = await customHomepage.find();
* ```
*
* @see SingleTypeManager
* @see StrapiClient
*/
single(resource: string) {
return new SingleTypeManager(resource, this._httpClient);
single(resource: string, options: SingleCollectionOptions = {}) {
const { path } = options;

return new SingleTypeManager({ resource, path }, this._httpClient);
}
}

// Local Client Types
export type ClientCollectionOptions = Pick<ContentTypeManagerOptions, 'path'>;
export type SingleCollectionOptions = Pick<ContentTypeManagerOptions, 'path'>;
Loading