Skip to main content

createResource

Resources are a collection of RestEndpoints that operate on a common data by sharing a schema

Usage

api/TodoResource.ts
export class Todo extends Entity {
id = '';
title = '';
completed = false;
pk() {
return this.id;
}
}

const TodoResource = createResource({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
schema: Todo,
});
Resources start with 6 Endpoints
const todo = useSuspense(TodoResource.get, { id: '5' });
const todos = useSuspense(TodoResource.getList);
controller.fetch(TodoResource.create, {
title: 'finish installing rest hooks',
});
controller.fetch(
TodoResource.update,
{ id: '5' },
{ ...todo, completed: true },
);
controller.fetch(TodoResource.partialUpdate, { id: '5' }, { completed: true });
controller.fetch(TodoResource.delete, { id: '5' });

Arguments

{
path: string;
schema: Schema;
Endpoint?: typeof RestEndpoint;
urlPrefix?: string;
} & EndpointExtraOptions

path

Passed to RestEndpoint.path

schema

Passed to RestEndpoint.schema

urlPrefix

Passed to RestEndpoint.urlPrefix

Endpoint

Class used to construct the members.

EndpointExtraOptions

Members

These provide the standard CRUD endpointss common in REST APIs. Feel free to customize or add new endpoints based to match your API.

get

  • method: 'GET'
  • path: path
  • schema: schema
// GET //test.com/api/abc/xyz
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).get({
group: 'abc',
id: 'xyz',
});

Commonly used with useSuspense(), Controller.invalidate

getList

  • method: 'GET'
  • path: shortenPath(path)
    • Removes the last argument:
      createResource({ path: '/:first/:second' }).getList.path === '/:first';
      createResource({ path: '/:first' }).getList.path === '/';
  • schema: [schema]
// GET //test.com/api/abc?isExtra=xyz
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).getList({
group: 'abc',
isExtra: 'xyz',
});

Commonly used with useSuspense(), Controller.invalidate

create

  • method: 'POST'
  • path: shortenPath(path)
    • Removes the last argument:
      createResource({ path: '/:first/:second' }).create.path === '/:first';
      createResource({ path: '/:first' }).create.path === '/';
  • schema: schema
// POST //test.com/api/abc
// BODY { "title": "winning" }
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).create(
{ group: 'abc' },
{ title: 'winning' },
);

Commonly used with Controller.fetch

update

  • method: 'PUT'
  • path: path
  • schema: schema
// PUT //test.com/api/abc/xyz
// BODY { "title": "winning" }
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).update(
{ group: 'abc', id: 'xyz' },
{ title: 'winning' },
);

Commonly used with Controller.fetch

partialUpdate

  • method: 'PATCH'
  • path: path
  • schema: schema
// PATCH //test.com/api/abc/xyz
// BODY { "title": "winning" }
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).partialUpdate(
{ group: 'abc', id: 'xyz' },
{ title: 'winning' },
);

Commonly used with Controller.fetch

delete

  • method: 'DELETE'
  • path: path
  • schema: new schema.Delete(schema)
  • process:
    (value, params) {
    return value && Object.keys(value).length ? value : params;
    },
// DELETE //test.com/api/abc/xyz
createResource({ urlPrefix: '//test.com', path: '/api/:group/:id' }).delete({
group: 'abc',
id: 'xyz',
});

Commonly used with Controller.fetch

Function Inheritance Patterns

To reuse code around Resource design, you can create your own function that calls createResource(). This has similar effects as class-based inheritance.

import {
createResource,
RestEndpoint,
type EndpointExtraOptions,
type RestGenerics,
} from '@rest-hooks/rest';

export class AuthdEndpoint<
O extends RestGenerics = any,
> extends RestEndpoint<O> {
getRequestInit(body: any): RequestInit {
return {
...super.getRequestInit(body),
credentials: 'same-origin',
};
}
}

export function createMyResource<U extends string, S extends Schema>({
path,
schema,
Endpoint = AuthdEndpoint,
...extraOptions
}: {
// `readonly` is critical for the argument types to be inferred correctly
readonly path: U;
readonly schema: S;
readonly Endpoint?: typeof RestEndpoint;
urlPrefix?: string;
} & EndpointExtraOptions) {
const BaseResource = createResource({
path,
Endpoint,
schema,
...extraOptions,
});

return {
...BaseResource,
getList: BaseResource.getList.extend({
schema: { results: [schema], total: 0, limit: 0, skip: 0 },
}),
};
}

The Github Example App uses this pattern as well.