Write a Request Builder
Use this skill whenever you're about to write a request for store.request or a <Request /> component, add a builder, or change one. A builder is a function that returns a request object. Together, an app's builders are its SDK for its API, so write each one as a documented, typed, reusable entry point, not a one-off object.
Steps
- Check whether the app already has a builder for this request before writing one. Look in the app's builders directory or package, often
builders/or#/builders. If one exists, call it instead of writing a second way to make the same request. - Don't call the general-purpose builders in
@warp-drive/utilities(json-api,rest,active-record) or@warp-drive/legacy/compat/buildersdirectly from components or app code. They exist for migrating to the request pipeline and for getting started, and can't document or type what a specific request is for. When one fits the endpoint, call it inside your own specific builder, such asgetUser(id)wrappingfindRecord('user', id). - Write the new builder, one exported function per file, in the app's builders directory. Keeping builders in one place is what lets the rest of the app, and other apps, find and reuse them.
- Name it for what it does, following the app's existing convention:
getCompanyPreviewList,searchUsers,createContentLike. The name is how other code discovers the builder through autocomplete. - Give it a doc comment describing its contract: what the request is for, the endpoint and method, what each argument means, what comes back, and how it is paged and sorted, using the API's own paging parameters. For a query, say which resource types make it stale when a record of that type is created, and set those types in
cacheOptions.types. Editors show this comment at every call site. - Set the response type inside the builder, using a helper from
@warp-drive/core/request. UsewithReactiveResponse<T>()when the response holds resources with a registered schema, since those come back as a reactive document whosedataisT. UsewithResponseType<T>()for anything else, withTas the full response type. Never add the type at the call site, by callingwithResponseTypethere or casting the result withas: that is a cast, and a builder is the only way to type a request without one. - Keep it pure, and make it produce the same RequestKey for the same arguments. Build query strings with
buildQueryParamsfrom@warp-drive/utilities, not by hand. For aPOSTor other non-GETquery, setcacheOptions.keyfrom a stable serialization of the arguments. Without a stable key, identical requests are neither deduplicated nor cached. - For a paginated collection, have the builder request the first page only. Don't add a
page,offsetorcursorargument: load later pages by following the response'slinks, withnext()on the reactive document or the experimental pagination primitives. For aPOSTorQUERYwhose server returns no links, the server should persist the query or encode it into a cursor link; if it can't, add a handler that generates the links, as described in Paginating With Links. - At the call site, pass the builder's result straight to
store.request(...)or to thequeryargument of the<Request />component (@queryin Ember). Fetch and Cache Data shows how to render the result. When several components need the same data, have each call the builder itself instead of loading it once and passing it down. Requests with a matching RequestKey share one response.
Example
ts
// builders/search-users.ts
import { withReactiveResponse } from '@warp-drive/core/request';
import { buildQueryParams } from '@warp-drive/utilities';
import type { UserPreview } from '#/data/types';
/**
* Searches users by name or email.
*
* - Endpoint: `GET /api/users`
* - Returns at most 20 {@link UserPreview}s, sorted by name, ascending.
*
* @param term text matched against the user's name and email
*/
export function searchUsers(term: string) {
const params = buildQueryParams({ search: term, sort: 'name', 'page[limit]': 20 });
return withReactiveResponse<UserPreview[]>({ url: `/api/users?${params}` });
}Related
- Full guide: Builders
- Typing: Typing Requests
- Making the request once the builder exists: Fetch and Cache Data