Documentation
Getting Started

Fuse.js Introduction

Fuse.js is an open-source library for building data layers. (star us on GitHub! (opens in a new tab)) It empowers frontend & mobile teams to build and own the central translatation layer from the backend microservices, data stores, & third-party APIs to the optimal GraphQL API for their websites, web apps, and mobile apps.

Fuse.js combines the best tooling from the GraphQL ecosystem to guide you to an organizational and technical “pit of success.” While it uses GraphQL (because it is the best technical choice) and familiarity with GraphQL will make for a minimal learning curve, you do not need to know GraphQL to use Fuse.js.

Getting Started

Before you begin

Before you start using Fuse.js, you need to have:

  • Familiarity with TypeScript
  • A Next.js app*

*Note that a Fuse.js data layer can also be developed and deployed outside of Next.js. However, our current focus is on making the experience with Next.js great, so expect rough edges elsewhere.

Setting up your Fuse data layer

Install the npm packages

npm install --save fuse graphql
npm install --save-dev @graphql-typed-document-node/core

Add the Next.js plugin to your next.config.js

const { nextFusePlugin } = require('fuse/next/plugin')
 
/** @type {import('next').NextConfig} */
const nextConfig = nextFusePlugin()({
  // Your Next.js config here
})
 
module.exports = nextConfig

Create the /api/fuse API route

This API route will serve as the entrypoint to the GraphQL API that Fuse.js creates. If you are using Next.js’s app router, add a file at app/api/fuse/route.ts and copy the below code to it:

import { createAPIRouteHandler } from 'fuse/next'
 
// NOTE: This makes Fuse.js automatically pick up every type in the /types folder
// Alternatively, you can manually import each type in the /types folder and remove this snippet
// @ts-expect-error
const files = require.context('../../../types', true, /\.ts$/)
files
  .keys()
  .filter((path: string) => path.includes('types/'))
  .forEach(files)
 
const handler = createAPIRouteHandler()
 
export const GET = handler
export const POST = handler
📄

If you are using Next.js's Pages Router, replace createAPIRouteHandler with createPagesRouteHandler instead.

That’s it! Fuse.js will now serve a GraphQL API at /api/fuse.

Adding your first type

Create a types folder at the root of your Next.js app and add a file at types/User.ts that contains the following code:

import { node } from 'fuse'
 
type UserSource = {
  id: string
  name: string
  avatar_url: string
}
 
// "Nodes" are the core abstraction of Fuse.js. Each node represents
// a resource/entity with multiple fields and has to define two things:
// 1. load(): How to fetch from the underlying data source
// 2. fields: What fields should be exposed and added for clients
export const UserNode = node<UserSource>({
  name: 'User',
  load: async (ids) => getUsers(ids),
  fields: (t) => ({
    name: t.exposeString('name'),
    // rename to camel-case
    avatarUrl: t.exposeString('avatar_url'),
    // Add an additional firstName field
    firstName: t.string({
      resolve: (user) => user.name.split(' ')[0],
    }),
  }),
})
 
// Fake function to fetch users. In real applications, this would
// talk to an underlying REST API/gRPC service/third-party API/…
async function getUsers(ids: string[]): Promise<UserSource[]> {
  return ids.map((id) => ({
    id,
    name: `Peter #${id}`,
    avatarUrl: `https://i.pravatar.cc/300?u=${id}`,
  }))
}

Note how the only code you had to write was the translation from the underlying data source to the shape of the data that you need. Fuse.js takes care of everything else for you.

If you now open localhost:3000/api/fuse in the browser, you'll see GraphiQL and be able to query for a user:

Screenshot of GraphiQL running at localhost:3000/api/fuse showing a query for a user, their id, name, and firstName, and the corresponding result

Querying your data layer

Fuse.js offers three entry-points to querying your data, depending on your use case:

  • React-Server-Components fuse/next/server
  • React-Client-Components in the /app directory fuse/next/client
  • React-Client Components in the /pages directory fuse/next/pages

React-Server-Components

When we are using react-server-components we have to register our client before we can start querying.

import { registerClient, createClient, fetchExchange } from 'fuse/next/server'
 
const { getClient } = registerClient(() =>
  createClient({
    url: 'http://localhost:3000/api/fuse',
    exchanges: [fetchExchange],
  }),
)

In doing so we register a client with React.cache that can be re-used across server-components.

import { graphql } from '@/fuse'
 
const UserQuery = graphql(`
  query User($id: ID!) {
    user(id: $id) {
      id
      name
      firstName
      avatarUrl
    }
  }
`)
 
export default async function Page() {
  const result = await getClient().query(UserQuery, {
    id: '1',
  });
}

This is the base way to query your components through server-components, you can now pass on the data to child components, ...

React-Client-Components in the /app directory

When you are leveraging the use client; directive in a /app component we have opted out of using server-components. This means we are going back to the traditional way of distributing our client over React.context.

It is advisable to create a Provider component with "use client"; that you use in your root-layout component so we are enabled to query data in any client page.

import {
  Provider,
  ssrExchange,
  cacheExchange,
  fetchExchange,
  createClient,
} from 'fuse/next/client'
import React from 'react'
 
export const DatalayerProvider = (props: any) => {
  const [client, ssr] = React.useMemo(() => {
    const ssr = ssrExchange()
    const client = createClient({
      url: 'http://localhost:3000/api/fuse',
      exchanges: [cacheExchange, ssr, fetchExchange],
      suspense: true,
    })
 
    return [client, ssr]
  }, [])
 
  return (
    <Provider client={client} ssr={ssr}>
      {props.children}
    </Provider>
  )
}

Let's add this to app/layout.tsx so we are enabled to query data in any subsequent page. Querying data cna be done by using the useQuery hook from fuse/next/client.

import { useQuery } from 'fuse/next/client'
 
function User() {
  const [result] = useQuery({
    query: UserQuery,
    variables: { id: '1' },
  })
}

Linearly, we supply useMutation for your mutation needs.

When you mutate data that affects a server-component you will need to call router.refresh() to re-render your server-component.

React-Client-Components in the /pages directory

Similar to the /app directory we can leverage useQuery the difference being that for server-side data we will query manually from getServerSideProps or getStaticProps and pass it into the component.

import {
  useQuery,
  withGraphQLClient,
  initGraphQLClient,
  ssrExchange,
  cacheExchange,
  fetchExchange,
} from 'fuse/next/pages'
 
function User() {
  const [result] = useQuery({
    query: UserQuery,
    variables: { id: '1' },
  })
}
 
export async function getServerSideProps() {
  const ssrCache = ssrExchange({ isClient: false })
  const client = initGraphQLClient({
    url: 'http://localhost:3000/api/fuse',
    exchanges: [cacheExchange, ssrCache, fetchExchange],
  })
 
  await client.query(UserQuery, { id: '1' }).toPromise()
 
  const graphqlState = ssrCache.extractData()
 
  return {
    props: {
      graphqlState,
    },
  }
}
 
export default withGraphQLClient((ssrCache) => ({
  url: 'http://localhost:3000/api/fuse',
  exchanges: [cacheExchange, ssrCache, fetchExchange],
}))(Page)

Adding in-line hints and validation (optional)

You can use @0no-co/graphqlsp to get inline hints while authoring GraphQL documents, you can do so by installing it and using the following in your tsconfig.json

{
  "name": "@0no-co/graphqlsp",
  "schema": "./schema.graphql",
  "disableTypegen": true,
  "templateIsCallExpression": true,
  "template": "graphql"
}

When using .vscode you will need to use the workspace version of TypeScript, to do so you can easily do that by creating .vscode/settings.json with the following content

{
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true
}