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.jsor if you use Remix:
npm install @makerkit/data-loader-supabase-remixUsage
To use the Data Loader SDK, you have a variety of options.
- JSX: you can use it as a component, importing the components
ServerDataLoaderandClientDataLoaderfrom the SDK. TheServerDataLoadercomponent can be used for Server Components (RSC), while theClientDataLoadercomponent will load data in Client Components. The Remix version only provides theClientDataLoadercomponent, as Remix doesn't support Server Components. - Hooks: you can use the
useSupabaseQueryto load data in your components. The Next.js usesSWRunder the hood, while the Remix version uses TanStack'suseQueryhook. - Directly: you can use the
fetchDataFromSupabasefunction 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
ServerDataLoadercomponent from the Data Loader SDK. - Import the
getSupabaseServerComponentClientfunction 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
clientprop to theServerDataLoadercomponent. - Pass the
tableprop to theServerDataLoadercomponent. In our case, we want to load data from theorganizationstable. - Define the fields that you want to load from the table using the
selectprop. In our case, we want to load theidandnamefields. - Pass the fetched data to the
dataprop of theDataTablecomponent - 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
selectprop 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
selectprop provides intellisense for the fields that you can select from the table. This depends on theclientproperty: 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
ServerDataLoadercomponent provides pagination out of the box. You can use thecount,pageSize, andpageCountproperties to display pagination controls. In Makerkit'sDataTablecomponent, 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!