I am super excited to announce the Data Loader SDK for Supabase, a new open source project that I have been working on for the past few weeks.
The Data Loader SDK is a declarative, type-safe set of utilities to load data from your Supabase database.
It's an external package that you can use in any Next.js or Remix project, and it's built on top of the Supabase JS Client. It's not limited to Makerkit's SaaS Kits.
Why?
Generally speaking - to remove boilerplate and make it easier to load data from your Supabase database. The SDK is built on top of the Supabase JS Client and uses the Supabase JS Client under the hood.
Installation
You can use the Data Loader SDK in any Next.js or Remix project. Yes - it's not limited to Makerkit's SaaS Kits.
To install it for Next.js, run the following command:
npm install @makerkit/data-loader-supabase-next.js
or if you use Remix:
npm install @makerkit/data-loader-supabase-remix
Usage
To use the Data Loader SDK, you have a variety of options.
- JSX: you can use it as a component, importing the components
ServerDataLoader
andClientDataLoader
from the SDK. TheServerDataLoader
component can be used for Server Components (RSC), while theClientDataLoader
component will load data in Client Components. The Remix version only provides theClientDataLoader
component, as Remix doesn't support Server Components. - Hooks: you can use the
useSupabaseQuery
to load data in your components. The Next.js usesSWR
under the hood, while the Remix version uses TanStack'suseQuery
hook. - Directly: you can use the
fetchDataFromSupabase
function from the core library to load data directly where you need it: for example, in API Routes, Server Actions, Remix Loaders/Actions, a Middleware, and so on.
Examples
Loading Supabase Data from a Server Component
In the example below, we want to pull data from a Supabase Database from within a Next.js Server Component. The data will be entirely fetched and rendered on the server.
Here's what we want to do:
- Import the
ServerDataLoader
component from the Data Loader SDK. - Import the
getSupabaseServerComponentClient
function to load the Supabase Client to be used in the Server Component. Ideally, you have provided the Database interface - so that the Loader can infer the types of the data that you are loading. - Pass the
client
prop to theServerDataLoader
component. - Pass the
table
prop to theServerDataLoader
component. In our case, we want to load data from theorganizations
table. - Define the fields that you want to load from the table using the
select
prop. In our case, we want to load theid
andname
fields. - Pass the fetched data to the
data
prop of theDataTable
component - which displays the data in a table.
import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs';import getSupabaseServerComponentClient from '~/core/supabase/server-component-client';import DataTable from '~/core/ui/DataTable';const OrganizationsTable = () => { const client = getSupabaseServerComponentClient(); return ( <ServerDataLoader client={client} table="organizations" select={['id', 'name']} > {({ data, count, pageSize, pageCount }) => { return ( <DataTable data={data} count={count} pageSize={pageSize} pageCount={pageCount} columns={[ { header: 'ID', accessorKey: 'id', }, { header: 'Name', accessorKey: 'name', }, ]} /> ); }} </ServerDataLoader> );};
DX Goodies:
- Type Safety: the
select
prop is type-safe. If you try to select a field that doesn't exist in the table, you will get a TypeScript error. - Intellisense: the
select
prop provides intellisense for the fields that you can select from the table. This depends on theclient
property: you must pass the Database types to the Supabase Client to get intellisense. - Server-Side Rendering: the data is fetched and rendered on the server.
- Pagination: the
ServerDataLoader
component provides pagination out of the box. You can use thecount
,pageSize
, andpageCount
properties to display pagination controls. In Makerkit'sDataTable
component, the pagination will automatically handled for you.
As you can see in the below image, the IDE will provide intellisense for the fields that you can select from the table.
Loading Supabase Data from a Client Component
In the example below, we want to pull data from a Supabase Database from within a Next.js Client Component. The data will be fetched on the client.
Generally speaking, the two components are very similar. The main difference is that we use the ClientDataLoader
component we need to handle the loading
state of the component - since the data is fetched on the client and is asynchronously loaded.
Below is an example of how you can use the ClientDataLoader
component to load data from a Supabase Database from within a Next.js Server Component. The Server component passes the page
query parameter to the ClientDataLoader
, which is used to load the page of data that we want to display.
import useSupabase from '~/core/supabase/use-supabase';import { ClientDataLoader } from '@makerkit/data-loader-supabase-nextjs';interface SearchParams { page: string;}const OrganizationsTable = ({ searchParams,}: { searchParams: SearchParams;}) => { const client = useSupabase(); const page = Number(searchParams.page) || 1; return ( <ClientDataLoader client={client} table="organizations" page={page} // the page to fetch select="*" // all the columns - can be omitted limit={10} // retrieve 10 organizations per page > {({ result, isLoading }) => { const { data, count, pageSize, pageCount } = result; if (isLoading) { return <span>Loading...</span>; } return ( <DataTable data={data} count={count} pageSize={pageSize} pageCount={pageCount} columns={[ { header: 'ID', accessorKey: 'id', }, { header: 'Name', accessorKey: 'name', }, ]} /> ); }} </ClientDataLoader> );};
Loading Supabase Data from a Hook
In the example below, we want to pull data from a Supabase Database from within a Next.js Client Component using a React Hook `useSupabaseQ
Let's define a React Hook that we can use to load data from the organizations
table.
import useSupabase from '~/core/supabase/use-supabase';import { useSupabaseQuery } from '@makerkit/data-loader-supabase-nextjs';export function useFetchOrganization() { const client = useSupabase(); return useSupabaseQuery({ client, table: 'organizations', select: ['id', 'name'], });}
We can then use the useFetchOrganization
hook in our components to load the data.
import { useFetchOrganization } from '~/hooks/use-fetch-organization';const OrganizationsTable = () => { const { result, isLoading } = useFetchOrganization(); const { data, count, pageSize, pageCount } = result; if (isLoading) { return <span>Loading...</span>; } return ( <DataTable data={data} count={count} pageSize={pageSize} pageCount={pageCount} columns={[ { header: 'ID', accessorKey: 'id', }, { header: 'Name', accessorKey: 'name', }, ]} /> );};
Loading Supabase Data Directly
Finally, you can also fetch data from anywhere by using the fetchDataFromSupabase
function from the core library @makerkit/data-loader-supabase-core
.
In the example below, we fetch the data from a Next.js API Route. We also pass the textSearch
operator to the name
field, which will perform a full-text search on the name
field.
import { NextResponse } from 'next/server';import { fetchDataFromSupabase } from '@makerkit/data-loader-supabase-core';import getSupabaseRouteHandlerClient from '~/core/supabase/route-handler-client';export async function GET() { const client = getSupabaseRouteHandlerClient(); const { data, count, pageSize, pageCount } = await fetchDataFromSupabase({ client, table: 'organizations', where: { name: { textSearch: `'makerkit'`, }, }, }); return NextResponse.json({ data, count, pageSize, pageCount, });}
Fetching a single Object from Supabase
To fetch a single item, instead of a list of items, you can use the single
prop. This will return a single object instead of an array of objects.
import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs';import getSupabaseServerComponentClient from '~/core/supabase/server-component-client';const Organization = () => { const client = getSupabaseServerComponentClient(); return ( <ServerDataLoader client={client} table="organizations" select={['id', 'name']} single > {({ data }) => { return ( <div> <h1>{data.name}</h1> </div> ); }} </ServerDataLoader> );};
The data
prop will be a single object instead of an array of objects.
Converting the data to camelCase
As it's commonly done in Postgres, the column names are in snake_case. If you want to convert the column names to camelCase (as it is more commonly done in Javascript), you can use the camelCase
prop.
import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs';import getSupabaseServerComponentClient from '~/core/supabase/server-component-client';const Task = () => { const client = getSupabaseServerComponentClient(); return ( <ServerDataLoader client={client} table="tasks" select={['name', 'created_at', 'updated_at']} single camelCase > {({ data }) => { // data will be in camelCase // { // id: '...', // createdAt: '...', // updatedAt: '...', // } return ( <div> <h1>{data.name} - {data.createdAt}</h1> </div> ); }} </ServerDataLoader> );};
Passing the SWR or React Query options
When fetching data using the ClientDataLoader
or the useSupabaseQuery
hook, you can pass the config
prop to the component. These props will be passed to the SWR
or React Query
hooks under the hood.
import { ClientDataLoader } from '@makerkit/data-loader-supabase-nextjs';import useSupabase from '~/core/supabase/use-supabase';const OrganizationsTable = () => { const client = useSupabase(); return ( <ClientDataLoader client={client} table="organizations" select={['id', 'name']} config={{ refreshWhenOffline: false, revalidateOnMount: false, }} > {({ data }) => { return ( <div> <h1>{data.name}</h1> </div> ); }} </ClientDataLoader> );};
You can do the same using the useSupabaseQuery
hook.
import { useSupabaseQuery } from '@makerkit/data-loader-supabase-nextjs';import useSupabase from '~/core/supabase/use-supabase';function useFetchOrganization() { const client = useSupabase(); return useSupabaseQuery({ client, table: 'organizations', select: ['id', 'name'], config: { refreshWhenOffline: false, revalidateOnMount: false, }, });}
This allows you to customize the behavior of the SWR
or React Query
hooks - for example, you can disable the revalidateOnMount
option to prevent the data from being fetched again when the component is mounted.
Conclusion
I hope you enjoyed this announcement. I am super excited about this new project, and I hope you will find it useful.
You can use it today in your Next.js or Remix projects. It's not limited to Makerkit's SaaS Kits - so feel free to use it in any project.
If you have any questions, feel free to reach out to me on our Discord server.
Happy coding!