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

searchParams

Passed to RestEndpoint.searchParams for getList and create

body

Passed to RestEndpoint.body for create update and partialUpdate

optimistic

true makes all mutation endpoints optimistic

6.7

Added in 6.7

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.Invalidate(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

Customizing Resources

createResource builds a great starting point, but often endpoints need to be further customized.

Overriding members with extends() makes this straightforward.

Because we are changing the endpoint types, we must use the {...spread} pattern rather than assignment. This is how TypeScript is able to infer the types from our arguments.

const TodoResourceBase = createResource({
path: '/todos/:id',
schema: Todo,
});

export const TodoResource = {
...TodoResourceBase,
getList: TodoResourceBase.getList.extend({
searchParams: {} as { userId?: string | number } | undefined,
}),
}

Explore more Rest Hooks demos

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.