JSPM

@kyvan2x/payload-rest-client

1.0.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • 0
  • Score
    100M100P100Q26588F
  • License MIT

A typesafe rest api client for the payload cms.

Package Exports

  • @kyvan2x/payload-rest-client
  • @kyvan2x/payload-rest-client/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@kyvan2x/payload-rest-client) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

payload-rest-client

A typesafe rest api client for the payload cms.

Quick Start

  1. Assume you have a users (auth enabled) and a posts collection with following fields:
interface User {
    id: string;
    email: string;
    name: string;
    password: string;
    createdAt: string;
    updatedAt: string;
}

interface Post {
    id: string;
    title: string;
    content: string;
    createdAt: string;
    updatedAt: string;
}
  1. Create the client:
import { createClient } from "@kyvan2x/payload-rest-client";
import { Config } from "./payload-types"; // auto generated types from payload

type Locales = "de" | "en";

const client = createClient<Config, Locales>({
    apiUrl: "http://localhost:4000/api",
});
  1. Now you can use all available queries for all collections and globals in a typesafe way:
// if you wan't to use protected routes, use login api...
const loginResponse = await client.collections.users.login({
    email: process.env.PAYLOAD_API_EMAIL,
    password: process.env.PAYLOAD_API_PASSWORD,
});

// ...and create another client with authorization header
const protectedClient = createClient<Config, Locales>({
    apiUrl: "http://localhost:4000/api",
    headers: {
        "Authorization": `Bearer ${loginResponse.token}`,
    },
});

const posts = await protectedClient.collections.posts.find({
    sort: "title", // only top level keys (optionally prefixed with "-") of Post allowed
    locale: "de", // only defined locales allowed
    limit: 10,
    page: 2,
});

console.log(posts); // type of posts is FindResult<Post> 

Error Handling

The client provides two approaches for error handling:

1. Traditional try/catch approach

import { 
    PayloadClientError, 
    ValidationError, 
    UnauthorizedError, 
    NotFoundError 
} from "@kyvan2x/payload-rest-client";

try {
    const user = await client.collections.users.create({
        doc: { email: "invalid-email", name: "Test" }
    });
} catch (error) {
    if (error instanceof ValidationError) {
        // Get structured validation errors
        const fieldErrors = error.getFieldErrors();
        console.log("Field errors:", fieldErrors);
        
        // Get full error data
        const errorData = error.getErrorData();
        console.log("Error details:", errorData);
    } else if (error instanceof UnauthorizedError) {
        console.log("Authentication required");
    } else if (error instanceof PayloadClientError) {
        console.log(`API Error (${error.statusCode}):`, error.message);
        console.log("Response data:", error.data);
    }
}

2. Functional error handling with client.safe

For a more functional approach without try/catch blocks, use the safe version of the client:

// Using the safe client - returns [error, undefined] or [undefined, result]
const [error, user] = await client.safe.collections.users.create({
    doc: { email: "invalid-email", name: "Test" }
});

if (error) {
    if (error instanceof ValidationError) {
        const fieldErrors = error.getFieldErrors();
        console.log("Validation failed:", fieldErrors);
    } else {
        console.log("Error:", error.message);
    }
    return;
}

// user is guaranteed to be defined here
console.log("User created:", user.doc);

3. Using the standalone tryCatch utility

You can also use the tryCatch utility function directly:

import { tryCatch, ValidationError, UnauthorizedError } from "@kyvan2x/payload-rest-client";

// Catch specific error types
const [error, result] = await tryCatch(
    client.collections.users.create({ doc: userData }),
    [ValidationError, UnauthorizedError]
);

if (error) {
    // Handle specific errors
    console.log("Caught expected error:", error.message);
} else {
    // Success case
    console.log("User created:", result.doc);
}

// Catch all errors
const [anyError, posts] = await tryCatch(
    client.collections.posts.find()
);

if (anyError) {
    console.log("Something went wrong:", anyError.message);
} else {
    console.log("Posts:", posts.docs);
}

Error Types

  • PayloadClientError: Base error class with statusCode, data, and response properties
  • ValidationError (400): Validation errors with field-specific details
  • UnauthorizedError (401): Authentication required
  • ForbiddenError (403): Access denied
  • NotFoundError (404): Resource not found
  • ConflictError (409): Resource conflict
  • ServerError (500+): Server-side errors

Custom Endpoints

  1. Define input and output types for alle custom endpoints:
import { CustomEndpoint } from "@kyvan2x/payload-rest-client";

/**
 * shape of generic CustomEndpoint type
 *
 * type Input = {
 *     params?: Record<string, string>;
 *     query?: Record<string, any>;
 *     body?: any;
 * };
 *
 * type Output = any;
 *
 * type CustomEndpoint<Input, Output>;
 */

type CustomEndpoints = {
    greet: CustomEndpoint<{
        params: { name: string };
        query: { locale: Locales };
    }, string>,
};
  1. Add it to createClient function:
-const client = createClient<Config, Locales>({
-    apiUrl: "http://localhost:4000/api",
-});
+const client = createClient<Config, Locales, CustomEndpoints>({
+    apiUrl: "http://localhost:4000/api",
+    customEndpoints: {
+        greet: { method: "GET", path: params => `hello/${params.name}` },
+    },
+});
  1. Call custom endpoints like this:
// Traditional approach
try {
    const greeting = await client.custom.greet({
        params: { name: "John Doe" },
        query: { locale: "en" },
    });
    console.log(greeting);
} catch (error) {
    console.error("Failed to greet:", error);
}

// Safe approach
const [error, greeting] = await client.safe.custom.greet({
    params: { name: "John Doe" },
    query: { locale: "en" },
});

if (error) {
    console.error("Failed to greet:", error.message);
} else {
    console.log("Greeting:", greeting);
}

API

Full documentation of the rest api

Client options

  • apiUrl: string - Base URL for the Payload API
  • cache?: RequestCache - Cache strategy for requests
  • headers?: HeadersInit - Default headers to include with requests
  • debug?: boolean - Enable debug logging
  • getAdditionalFetchOptions?: (params: GetAdditionalFetchOptionsParams) => any - Function to add additional fetch options
  • customFetchFn?: (input: RequestInfo | URL, init?: RequestInit) => Promise - Custom fetch function
  • customEndpoints?: Record<String, CustomEndpointFactory> - Custom endpoint definitions

Collections

  • find: (params?: FindParams<T, LOCALES>) => Promise<FindResult>
  • findById: (params: FindByIdParams) => Promise
  • count: (params?: CountParams) => Promise
  • create: (params: CreateParams<T, LOCALES>) => Promise<CreateResult>
  • createDraft: (params: CreateDraftParams<T, LOCALES>) => Promise<CreateDraftResult>
  • update: (params: UpdateParams<T, LOCALES>) => Promise<UpdateResult>
  • updateById: (params: UpdateByIdParams<T, LOCALES>) => Promise<UpdateByIdResult>
  • delete: (params?: DeleteParams<T, LOCALES>) => Promise<DeleteResult>
  • deleteById: (params: DeleteByIdParams) => Promise

Collections with auth enabled (additional to above)

  • login: (params: LoginParams) => Promise<LoginResult>
  • logout: (params?: LogoutParams) => Promise
  • unlock: (params: UnlockParams) => Promise
  • refresh-token: (params?: RefreshTokenParams) => Promise
  • me: (params?: MeParams) => Promise<MeResult>
  • forgot-password: (params: ForgotPasswordParams) => Promise
  • reset-password: (params: ResetPasswordParams) => Promise<ResetPasswordResult>

Globals

  • get: (params?: BaseParams) => Promise
  • update: (params: UpdateGlobalParams<T, LOCALES>) => Promise

Others

  • access: () => Promise

Changelog

v 3.0.7

  • NEW: Added functional error handling with client.safe - no more try/catch needed!
  • NEW: Added tryCatch utility function for manual error handling
  • NEW: All client methods now available in both traditional and safe versions
  • BREAKING: Client now returns an object with both regular and safe APIs
  • Added: Complete TypeScript support for safe error handling patterns
  • Added: Comprehensive examples for both error handling approaches

v 3.0.6

  • BREAKING: Improved error handling with structured error types
  • BREAKING: Better query string encoding with proper URL encoding
  • BREAKING: Enhanced type safety for auth endpoints (optional parameters)
  • Fixed: JSON error responses are now properly parsed and preserved as objects
  • Fixed: Content-Type header is only added when there's a request body
  • Fixed: Better handling of network errors and malformed responses
  • Fixed: Improved debug logging with more detailed information
  • Added: Comprehensive error types with field-specific validation errors
  • Added: Better handling of array parameters in query strings
  • Added: Input validation for client options

v 3.0.5

  • Infer id params (string or number) from type.

v 3.0.4

  • Added custom endpoints

v 3.0.3

  • Added option to use custom fetch function

v 3.0.2

  • Export error types
  • Added access api

v 3.0.1

  • Better type inference for joins

v 3.0.0

  • Payload 3 (for Payload 2 use older versions)
  • Added select, populate and join params
  • Added count api