# Announcing the Data Loader SDK for Supabase

> We're excited to announce the Data Loader SDK for Supabase. It's a declarative, type-safe set of utilities to load data into your Supabase database that you can use in your Next.js or Remix apps.

*Published: 2023-12-27*
*Canonical: https://makerkit.dev/blog/tutorials/data-loader-sdk*

---

I am super excited to announce the **Data Loader SDK for Supabase**, a new [open source project](https://github.com/[makerkit](https://makerkit.dev/ai-saas-starter)/[makerkit](/courses/nextjs-turbo/introduction)) 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:

```bash
npm install @makerkit/data-loader-supabase-next.js
```

or if you use Remix:

```bash
npm install @makerkit/data-loader-supabase-remix
```

## Usage

To use the Data Loader SDK, you have a variety of options.

1. **JSX**: you can use it as a component, importing the components `ServerDataLoader` and `ClientDataLoader` from the SDK. The `ServerDataLoader` component can be used for Server Components (RSC), while the `ClientDataLoader` component will load data in Client Components. The Remix version only provides the `ClientDataLoader` component, as Remix doesn't support Server Components.
2. **Hooks**: you can use the `useSupabaseQuery` to load data in your components. The Next.js uses `SWR` under the hood, while the Remix version uses TanStack's `useQuery` hook.
3. **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:
1. Import the `ServerDataLoader` component from the Data Loader SDK.
2. 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.
3. Pass the `client` prop to the `ServerDataLoader` component.
4. Pass the `table` prop to the `ServerDataLoader` component. In our case, we want to load data from the `organizations` table.
5. Define the fields that you want to load from the table using the `select` prop. In our case, we want to load the `id` and `name` fields.
6. Pass the fetched data to the `data` prop of the `DataTable` component - which displays the data in a table.

```tsx
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:

1. **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.
2. **Intellisense**: the `select` prop provides intellisense for the fields that you can select from the table. This depends on the `client` property: you must pass the Database types to the Supabase Client to get intellisense.
3. **Server-Side Rendering**: the data is fetched and rendered on the server.
4. **Pagination**: the `ServerDataLoader` component provides pagination out of the box. You can use the `count`, `pageSize`, and `pageCount` properties to display pagination controls. In Makerkit's `DataTable` 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.

{% img src="/assets/images/posts/data-loader-sdk-tasks-example.webp" alt="Intellisense for the select prop" width="1048" height="1080" /%}

### 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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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.

```tsx
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!
