API Client
@spa-tools/api-client excels in calling HTTP API endpoints with features specifically designed for modern, data-driven web applications. If your SPA has data workflows requiring calls to backend APIs (public and/or private), then this tool may be just what the doctor ordered.
Throttling
Caching
State Interpolation
Result Mapping
React (or not)
TypeScript First
Zero Dependencies
14 kB (index.cjs)
Tree-shakable
Installation
To install API Client in your project, use your favorite package manager and run the respective command:
- npm
- Yarn
- pnpm
npm install @spa-tools/api-client
yarn add @spa-tools/api-client
pnpm add @spa-tools/api-client
Quickstart
Choose your preferred implementation:
- +
- +
import { callEndpoint } from '@spa-tools/api-client';
// define our data shape
export interface AlbumPhoto {
albumId: number;
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
// an engineer-friendly function to retrieve photos by album ID
export async function getAlbumPhotos(albumId: number) {
//
// the callEndpoint function is very similar to the fetch API but
// with a variety of powerful features for request and response
// (see Reference and Guides for more info).
//
// notice that we're specifying an array of AlbumPhoto objects
// as the expected result data type which will give us very nice
// intellisense when accessing the returned data; we could also
// specify a custom error type as well if we wanted to
const result = await callEndpoint<AlbumPhoto[]>(
// we use an endpoint template here with a path param
// which will auto-interpolate using the passed-in state
'https://jsonplaceholder.typicode.com/albums/:albumId/photos',
// this second argument contains the object we want to use to inject
// state into the URL and as long as the state property names match
// the path param names, they will auto-interpolate
{ albumId }
);
return result;
}
const albumId = 1;
const result = await getAlbumPhotos(albumId);
// the API Client calls always return a standardized result envelope
// that includes a data and an error property; while this structure
// cannot be changed, the interfaces of the underlying data and error
// types are 100% up to you and your API
if (result.data) {
console.log(`Photos for album with ID "${albumId}":`);
console.log(result.data);
} else if (result.error) {
console.error(`Error retrieving photos for album with ID "${albumId}":`);
console.error(result.error);
}
import { callEndpoint } from '@spa-tools/api-client';
// an engineer-friendly function to retrieve photos by album ID
export async function getAlbumPhotos(albumId) {
//
// the callEndpoint function is very similar to the fetch API but
// with a variety of powerful features for request and response
// (see Reference and Guides for more info).
//
const result = await callEndpoint(
// we use an endpoint template here with a path param
// which will auto-interpolate using the passed-in state
'https://jsonplaceholder.typicode.com/albums/:albumId/photos',
// this second argument contains the object we want to use to inject
// state into the URL and as long as the state property names match
// the path param names, they will auto-interpolate
{ albumId }
);
return result;
}
const albumId = 1;
const result = await getAlbumPhotos(albumId);
// the API Client calls always return a standardized result envelope
// that includes a data and an error property; while this structure
// cannot be changed, the interfaces of the underlying data and error
// types are 100% up to you and your API
if (result.data) {
console.log(`Photos for album with ID "${albumId}":`);
console.log(result.data);
} else if (result.error) {
console.error(`Error retrieving photos for album with ID "${albumId}":`);
console.error(result.error);
}
import { useCallEndpoint } from '@spa-tools/api-client';
// define our data shape
export interface AlbumPhoto {
albumId: number;
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
// an engineer-friendly hook to retrieve photos by album ID
function useGetAlbumPhotos() {
//
// hook wrappers are really this easy to create!
//
// notice that we're specifying an array of AlbumPhoto objects
// as the expected result data type which will give us very nice
// intellisense when accessing the returned data; we could also
// specify a custom error type as well if we wanted to
return useCallEndpoint<AlbumPhoto[]>('https://jsonplaceholder.typicode.com/albums/:albumId/photos');
}
function AlbumThumbnails({ albumId }: { albumId: number }) {
const [getAlbumPhotos, albumPhotosResult, isAlbumPhotosCallPending, clearAlbumPhotos] = useGetAlbumPhotos();
const handleRetrieveAlbumPhotos = () => {
// trigger the API call
getAlbumPhotos({ albumId });
}
return (
<div>
<div>
<button onClick={handleRetrieveAlbumPhotos}>
Retrieve Album Photos
</button>
<button onClick={clearAlbumPhotos}>
Clear Album Photos
</button>
</div>
<div>
{isAlbumPhotosCallPending ? (
<div>Loading album photos...</div>
) : albumPhotosResult?.data ? (
<div>
{albumPhotosResult.data.map((photo) => (
<div key={photo.id}>
<img src={photo.thumbnailUrl} alt={photo.title} />
</div>
))}
</div>
) : albumPhotosResult?.error ? (
<div>Error: {albumPhotosResult.error}</div>
) : null}
</div>
</div>
)
}
import { useCallEndpoint } from '@spa-tools/api-client';
// an engineer-friendly hook to retrieve photos by album ID
function useGetAlbumPhotos() {
//
// hook wrappers are really this easy to create!
//
return useCallEndpoint('https://jsonplaceholder.typicode.com/albums/:albumId/photos');
}
function AlbumThumbnails({ albumId }) {
const [getAlbumPhotos, albumPhotosResult, isAlbumPhotosCallPending, clearAlbumPhotos] = useGetAlbumPhotos();
const handleRetrieveAlbumPhotos = () => {
// trigger the API call
getAlbumPhotos({ albumId });
}
return (
<div>
<div>
<button onClick={handleRetrieveAlbumPhotos}>
Retrieve Album Photos
</button>
<button onClick={clearAlbumPhotos}>
Clear Album Photos
</button>
</div>
<div>
{isAlbumPhotosCallPending ? (
<div>Loading album photos...</div>
) : albumPhotosResult?.data ? (
<div>
{albumPhotosResult.data.map((photo) => (
<div key={photo.id}>
<img src={photo.thumbnailUrl} alt={photo.title} />
</div>
))}
</div>
) : albumPhotosResult?.error ? (
<div>Error: {albumPhotosResult.error}</div>
) : null}
</div>
</div>
)
}