Nodes
The node
function is a core abstraction in fuse
, it allows you to express a way to load
a key-able* entity and its shape. By defining the way to load the entity we enable a few use-cases
- We can return the
key
of the node at any point in our graph and theload
function defined on thenode
will take care of resolving the full entity - We can return a list of
keys
when our list endpoint does not return the full entity and theload
function will take care of loading these in parallel.
A few things this endpoint does for you is create an automatic entry-point for the node
query-field as well as the lower-cased type
query-field (in this case user
) and
we'll ensure that the id
of this entity is globally unique.
Let's look at the example of the Getting Started guide:
import { node } from 'fuse'
type UserSource = {
id: string
name: string
avatarUrl: string
}
export const UserNode = node<
UserSource,
// This is the default, you can change this when you use a different type, like number.
// string
>({
name: 'User',
// This is the default, however if you use a different key-field property you can change this.
// key: 'id',
load: async (ids) => getUsers(ids),
fields: (t) => ({
name: t.exposeString('name'),
avatarUrl: t.exposeString('avatarUrl'),
firstName: t.string({
resolve: (user) => user.name.split(' ')[0],
}),
}),
})
If we were to have a list endpoint that only returned the name
and was missing avatarUrl
we could do the following:
addQueryFields((t) => ({
users: t.field({
type: [UserNode],
resolve: async () => {
const result = await listUsers()
return result.map(user => user.id);
}
}),
}))
Now the underlying API knows it needs to go back to the load
function and resolve all the details for these keys.
Similarly if we were to have an objectField
that needs to return a UserNode
we can just return the id
and
the dataloader (opens in a new tab) will ensure that these are loaded in parallel.
*key-able: A key-able entity is an entity that has a unique identifier that can be used to load the entity.
Example of querying the automatically generated entry-points:
query {
node(id: x) {
... on User { id firstName }
}
user(id: x) {
id
firstName
}
}
How to handle authorization
Authorization can be hard to reason about in these cases, this is why we thought about a few ways to handle this.
You can centralise authorization to the node
of an entity, this would mean
implementing the logic in the load
function. This means that every time
you need to go to the load
function to resolve an entity it will run
through the authorization logic by default. This does mean that if you need
a different contextual authority you can't just return the identifier.
Alternatively if you know that the underlying datasource is already running authorization you can choose to defer that logic to the datasource.