@fbritoferreira/strapi
v0.4.0
Published
A generic TypeScript client for interacting with Strapi CMS APIs, supporting content fetching, creation, updates, and deletions.
Maintainers
Readme
@fbritoferreira/strapi
A lightweight, generic TypeScript client for Strapi CMS v5, supporting CRUD
operations, query parameters (filters, populate, pagination), and
internationalization (i18n) with locale-specific creates/updates. Built with
modern ES modules and Fetch API, it handles error responses and nested filters
via qs serialization.
Features
- Generic TypeScript Support: Define your entity types (e.g.,
{ id: number; name: string; documentId?: string }) for full type safety on requests/responses. - CRUD Operations:
findMany,find,create,update,delete. - Upsert: Atomic create-or-update based on filters.
- i18n Handling: Automatic default locale creation and localization linking using numeric IDs.
- Query Params: Supports Strapi's filters (e.g.,
{ name: { $eq: 'foo' } }), populate (*), pagination, and locale. - Parallel Pagination: Use
all: trueto fetch all pages in parallel for better performance. - Error Handling: Returns
[ServiceError | null, Data | null]tuples for async operations. - No Dependencies: Only
qsfor query stringification; polyfills Fetch if needed.
Installation
npm install @fbritoferreira/strapipnpm add @fbritoferreira/strapiyarn add @fbritoferreira/strapiRequires Node.js >=18 for modern Fetch. For older environments, polyfill fetch
Quick Start
Import and instantiate the client with your Strapi base URL, optional auth
token, and content-type UID (e.g., articles for /api/articles)
import { StrapiClient } from "@fbritoferreira/strapi";
import type {
CreatePayload,
QueryParams,
StrapiFilters,
} from "@fbritoferreira/strapi/types";
interface Article {
id: number;
documentId?: string;
title: string;
content: string;
}
const client = new StrapiClient<Article>({
baseURL: "http://localhost:1337", // Strapi API base
uid: "articles", // UID for /api/articles
token: "your-jwt-token", // Optional for auth
});
// Find multiple
const [err1, articles] = await client.findMany({ populate: ["*"] });
if (!err1 && articles) {
console.log(articles); // Article[]
}
// Find one by ID
const [err2, article] = await client.find({ id: 1 });
if (!err2 && article) {
console.log(article); // Article | null
}
// Create (default locale 'en')
const payload: CreatePayload<Article> = {
data: { title: "New Article", content: "Hello World" },
};
const [err3, newArticle] = await client.create({ payload });
if (!err3 && newArticle) {
console.log(newArticle); // Article
}
// Upsert with filters (create if not exists)
const filters: StrapiFilters<Article> = { title: { $eq: "Existing Title" } };
const [err4, upserted] = await client.upsert({ payload, filters });
if (!err4 && upserted) {
console.log(upserted); // Article
}i18n Usage
Strapi i18n uses locales (default 'en'). For non-default locales, provide
locale option; the client auto-creates default if needed and links via base
ID.
// Create in French (searches/creates 'en' first if missing)
const [err, frArticle] = await client.create({
payload,
locale: "fr",
filters: { title: { $eqi: "Article Français" } }, // For existence check
});
if (!err && frArticle) {
console.log(frArticle.documentId); // Shared document ID for localizations
}
// Update specific locale
const updatePayload: UpdatePayload<Article> = {
data: { content: "Updated FR" },
};
const [err5, updated] = await client.update({
id: 1,
payload: updatePayload,
locale: "fr",
});
// Find with locale
const [err6, frArticles] = await client.findMany({
filters: { title: { $contains: "Français" } },
locale: "fr",
});Query Parameters
Pass QueryParams<Article> for filters, populate, etc. Nested filters use
$eq, $contains, etc.
const params: QueryParams<Article> = {
filters: { title: { $eq: "Exact Title" } },
populate: ["category", "author"],
pagination: { pageSize: 10 },
locale: "fr",
};
const [err, paginated] = await client.findMany(params);
// Fetch all pages in parallel (better performance for large datasets)
const [err2, allArticles] = await client.findMany({
params,
all: true,
});Error Handling
Methods return [ServiceError | null, Data | null]. Check err for issues like
404 or network failures.
const [err, data] = await client.find({ id: 999 });
if (err) {
console.error(err.message, err.status); // e.g., "Strapi API error: 404 Not Found"
}API Reference
See src/types.ts for full types. Key methods:
findMany(options: { params?: QueryParams<T>; locale?: string; all?: boolean }): Promise<[ServiceError | null, T[] | null]>find(options: { id?: number | string; params?: QueryParams<T>; locale?: string; all?: boolean }): Promise<[ServiceError | null, T | null]>create(options: { payload: CreatePayload<T>; params?: Omit<QueryParams<T>, 'filters'>; locale?: string; filters?: StrapiFilters<T> }): Promise<[ServiceError | null, T | null]>update(options: { id: number | string; payload: UpdatePayload<T>; params?: QueryParams<T>; locale?: string }): Promise<[ServiceError | null, T | null]>delete(options: { id: number | string }): Promise<[ServiceError | null, T | null]>upsert(options: { payload: CreatePayload<T>; filters?: StrapiFilters<T>; params?: Omit<QueryParams<T>, 'filters'>; locale?: string }): Promise<[ServiceError | null, T | null]>
Constructor:
new StrapiClient<T>({ baseURL: string, uid: string, token?: string })
Development
- Clone and install:
git clone <repo> && pnpm install - Run tests:
pnpm test(uses Vitest) - Build:
pnpm build(outputs todist/) - Dev server:
pnpm dev(for library previews)
Uses Vite for building and Vitest for testing.
Contributing
- Fork the repo.
- Create a feature branch (
git checkout -b feature/amazing-feature). - Commit changes (
git commit -m 'Add amazing feature'). - Push (
git push origin feature/amazing-feature). - Open a Pull Request.
License
Distributed under the MIT License. See LICENSE for more information.
