npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rixw/strapi-client

v0.4.2

Published

A generic client SDK for Strapi REST APIs

Downloads

95

Readme

Strapi Client

This is a generic REST API client for Strapi v4. It is being created with a couple of objectives:

  • Simplify the process of calling Strapi APIs from the frontend
  • Unwrap Strapi's complicated data structure and return a simple object or array of objects
  • Typing support Strapi's API parameters

Roadmap

  • [x] Basic fetch operations
  • [x] Automatic API parameter stringification
  • [x] Normalisation of Strapi's complex data structure
  • [x] Support for Strapi's Users & Permissions plugin
  • [x] Support for Strapi's API Tokens
  • [x] Create, update and delete operations
  • [x] Support for Strapi's internationalisation
  • [x] Custom error types
  • [x] Simplified API support for pagination
  • [x] Optional date conversion (currently forced)
  • [x] Built-in rate limiting
  • [ ] Simplified support for uploaded files
  • [ ] Improved API for specifying content types (use ts:generate-types)

Compatibility

This library is compatible with Strapi v4. It has been tested with Strapi v4.9 but should be backwards compatible to any v4 version.

Strapi v5 enables a simplified REST response, so the unwrapping in this library will not be necessary. I have not tested this library with Strapi v5.

Because of the different REST API response formats in Strapi v3 and v4, this library is not compatible with Strapi v3.

HTTP requests use Axios so can work on both client and server side.

Kudos to strapi-sdk-js

Credit should go to strapi-sdk-js. I started this client library before I realised this exists (although at time of writing there have been no updates since February 2022). I've taken some ideas from this library, although I've tried to improve the typing of API parameters and simplify the API around unwrapping Strapi's standard data responses.

Installation

Using npm:

$ npm install @rixw/strapi-client

Using yarn:

$ yarn add @rixw/strapi-client

Using pnpm:

$ pnpm add @rixw/strapi-client

Basic usage

import { StrapiClient } from '@rixw/strapi-client';
import { Page } from './types'; // Your own type definitions

// Instantiate the client
const client = new StrapiClient({
  baseURL: 'https://example.com/api', // Required root of the REST API
  contentTypes: ['page', 'post'], // Singular names of entities
});

// Login - sets the JWT token in the client if using Users & Permissions plugin
const jwt = await client.login('[email protected]', 'password');

// Retrieve a list of items
const pages = await client.fetchMany<Page>('page', {
  sort: 'title:asc',
  populate: '*',
});

// Retrieve a single item
const page = await client.fetchById<Page>('page', 1);

API

StrapiClient

constructor

{
  // The root of the Strapi REST API.
  baseURL: 'https://example.com/api',

  // An array of the content types in your API. This is used to map the UID of
  // the type to singular and plural names for building the API URLs.
  // Can be either simple singular names, in which case the UID is assumed to be
  // `api::entity.entity` and the path `/api/entities`. For other entries
  // including single types or plugin entities, use a longer-form object
  // definition.
  contentTypes: [
    'page',
    'post',
    {
      id: 'api::homepage.homepage',
      singularName: 'homepage',
      path: '/api/homepage',
    },
    {
      id: 'plugin::my-plugin.my-content-type',
      singularName: 'my-content-type',
      path: '/api/my-plugin/my-content-types',
    },
  ],

  // A JWT token to use for authentication. You can provide either Strapi's
  // long-lived API Tokens or, if you've cached a short-term JWT token from the
  // Users & Permissions plugin, you can provide that instead. Optional - if
  // not provided no Authorization header will be sent.
  jwt: 'xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',

  // An Axios config object to use for all requests. Optional. Allows overriding
  // the default timeout, headers, etc. You could provide an HttpAgent to
  // implement rate limiting, for example.
  // See https://github.com/axios/axios#request-config for more details.
  // Note that headers set by the client (Authorization, Accept, Content-Type)
  // will override any headers you provide here.
  axiosConfig?: {
    timeout: 1000,
    maxRedirects: 5,
    httpAgent: new http.Agent({ keepAlive: true }),
    httpsAgent: new https.Agent({ keepAlive: true }),
    proxy: {
      protocol: 'https',
      host: '127.0.0.1',
      port: 9000,
      auth: {
        username: 'proxyuser',
        password: 'proxypassword'
      },
    },
  },

  // If true, the client will log debug information to the console.
  // Default: false.
  debug: true,
}

login => Promise<string>

identifier (string) - the identifier to use for login (e.g. email address)

password (string) - the password to use for login

const jwt = await client.login({
  identifier: '[email protected]',
  password: 'password',
});

Convenience method to login to the Users & Permissions plugin. Makes a POST request to ${baseURL}/auth/local with the provided credentials and sets the JWT token in the client. Returns the JWT token.

fetchById<T extends StrapiEntity> => Promise<T>

uid (string) - the content type UID

id (number) - the ID of the entity to fetch

params (StrapiParams) - optional parameters to pass to the API

const page = await client.fetchById<Page>('page', 1);

Makes a GET request for the specified entity. Uses the contentTypes array to work out the URL. Provided params are passed to the API after conversion with the qs library as described in the Strapi docs. Enables you to specify populate, fields, filters etc without boilerplate. If you're using Typescript, this object is typed.

The response is unwrapped from Strapi's complicated data structure and returned as a simple object.

fetchMany<T extends StrapiEntity> => Promise<StrapiPaginatedArray<T>>

const pages = await client.fetchMany<Page>('page', {
  sort: 'title:asc',
  populate: '*',
});

Makes a GET request for the specified entities. Uses the contentTypes array to work out the URL. Provided params are passed to the API after conversion with the qs library as described in the Strapi docs. Enables you to specify populate, fields, filters etc without boilerplate. If you're using Typescript, this object is typed.

The response is unwrapped from Strapi's complicated data structure and returned as a StrapiPaginatedArray<T>, which is essentially a plain array of entities with an additional pagination property.

fetchSingle<T extends StrapiEntity> => Promise<T>

const pages = await client.fetchSingle<Page>('homepage', {
  populate: '*',
});

Makes a GET request for the specified single entity. Uses the contentTypes array to work out the URL. Provided params are passed to the API after conversion with the qs library as described in the Strapi docs. Enables you to specify populate, fields, filters etc without boilerplate. If you're using Typescript, this object is typed.

The response is unwrapped from Strapi's complicated data structure and returned as a simple object..

Normalisation

Strapi responses are unwrapped from Strapi's complicated data structure using a recursive function that extracts all data properties and merges the id and attributes of entities so that they are flat. This means that you can use returned entities in a more intuitive way, e.g. page.title instead of page.data.attributes.title.

Any properties whose name ends At or On and whose value is an ISO Date string are converted to Date objects.

Example array response normalisation:

Strapi response:

{
  "data": [
    {
      "id": 1,
      "attributes": {
        "title": "Root",
        "slug": "root",
        "createdAt": "2023-04-09T11:26:45.039Z",
        "updatedAt": "2023-04-09T11:26:49.426Z",
        "publishedAt": "2023-04-09T11:26:49.419Z",
        "child_pages": {
          "data": [
            {
              "id": 2,
              "attributes": {
                "title": "Node",
                "slug": "node",
                "createdAt": "2023-04-09T11:27:20.374Z",
                "updatedAt": "2023-04-09T11:27:26.629Z",
                "publishedAt": "2023-04-09T11:27:26.607Z"
              }
            }
          ]
        },
        "parent_page": {
          "data": null
        }
      }
    },
    {
      "id": 2,
      "attributes": {
        "title": "Node",
        "slug": "node",
        "createdAt": "2023-04-09T11:27:20.374Z",
        "updatedAt": "2023-04-09T11:27:26.629Z",
        "publishedAt": "2023-04-09T11:27:26.607Z",
        "child_pages": {
          "data": []
        },
        "parent_page": {
          "data": {
            "id": 1,
            "attributes": {
              "title": "Root",
              "slug": "root",
              "createdAt": "2023-04-09T11:26:45.039Z",
              "updatedAt": "2023-04-09T11:26:49.426Z",
              "publishedAt": "2023-04-09T11:26:49.419Z"
            }
          }
        }
      }
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "pageSize": 25,
      "pageCount": 1,
      "total": 2
    }
  }
}

becomes:

[
  {
    id: 1,
    title: 'Root',
    slug: 'root',
    createdAt: new Date('2023-04-09T11:26:45.039Z'),
    updatedAt: new Date('2023-04-09T11:26:49.426Z'),
    publishedAt: new Date('2023-04-09T11:26:49.419Z'),
    child_pages: [
      {
        id: 2,
        title: 'Node',
        slug: 'node',
        createdAt: new Date('2023-04-09T11:27:20.374Z'),
        updatedAt: new Date('2023-04-09T11:27:26.629Z'),
        publishedAt: new Date('2023-04-09T11:27:26.607Z'),
      },
    ],
    parent_page: null,
  },
  {
    id: 2,
    title: 'Node',
    slug: 'node',
    createdAt: new Date('2023-04-09T11:27:20.374Z'),
    updatedAt: new Date('2023-04-09T11:27:26.629Z'),
    publishedAt: new Date('2023-04-09T11:27:26.607Z'),
    child_pages: [],
    parent_page: {
      id: 1,
      title: 'Root',
      slug: 'root',
      createdAt: new Date('2023-04-09T11:26:45.039Z'),
      updatedAt: new Date('2023-04-09T11:26:49.426Z'),
      publishedAt: new Date('2023-04-09T11:26:49.419Z'),
    },
  },
];

The array also has a property pagination which contains the pagination (acessible as myArray.pagination):

pagination: {
  page: 1,
  pageSize: 25,
  pageCount: 1,
  total: 2
}

Example single entity normalisation:

Strapi response:

{
  "data": {
    "id": 1,
    "attributes": {
      "title": "Root",
      "slug": "root",
      "createdAt": "2023-04-09T11:26:45.039Z",
      "updatedAt": "2023-04-09T11:26:49.426Z",
      "publishedAt": "2023-04-09T11:26:49.419Z",
      "child_pages": {
        "data": [
          {
            "id": 2,
            "attributes": {
              "title": "Node",
              "slug": "node",
              "createdAt": "2023-04-09T11:27:20.374Z",
              "updatedAt": "2023-04-09T11:27:26.629Z",
              "publishedAt": "2023-04-09T11:27:26.607Z"
            }
          }
        ]
      },
      "parent_page": {
        "data": null
      }
    }
  }
}

becomes:

{
  id: 1,
  title: "Root",
  slug: "root",
  createdAt: new Date("2023-04-09T11:26:45.039Z"),
  updatedAt: new Date("2023-04-09T11:26:49.426Z"),
  publishedAt: new Date("2023-04-09T11:26:49.419Z"),
  child_pages: [
    {
      id: 2,
      title: "Node",
      slug: "node",
      createdAt: new Date("2023-04-09T11:27:20.374Z"),
      updatedAt: new Date("2023-04-09T11:27:26.629Z"),
      publishedAt: new Date("2023-04-09T11:27:26.607Z")
    }
  ],
  parent_page: null
}

Entity Types

If using Typescript, the fetchMany and fetchById methods are generic and take a type parameter which allows you to specify the type of the entity you are querying. There is a very simple interface defined for entities with just the basic Strapi entity fields:

interface StrapiEntity {
  id?: number;
  createdAt?: Date;
  updatedAt?: Date;
  publishedAt?: Date;
}