> Fetch data with list, byId, selects, and relations in the server client and hooks

# Querying

Fetch data on the server with the client from `createPyloServer` or `createPyloNode`, or in Client
Components with the React Query hooks. All of them share the same `select` and `filter` API.

## Server

### List

```typescript filename="app/projects/page.tsx"
import { pylo } from '@/lib/pylo';

const { data, pagination } = await pylo.project.list({
  select: {
    id: true,
    name: true,
    created_at: true,
  },
  pagination: { page: 1, per_page: 20 },
});

// data: Array<{ id: string; name: string | null; created_at: string }>
// pagination: { total, current_page, per_page, last_page, has_more_pages }
```

Omit `select` to get all scalar fields.

### By ID

```typescript filename="example.ts"
const project = await pylo.project.byId('project-uuid', {
  select: {
    id: true,
    name: true,
    customer: {
      select: { id: true, name: true },
    },
  },
});

// Returns null if not found
```

## Client hooks

### usePyloList

```tsx filename="components/project-list.tsx"
'use client';

import { pylo } from "@/lib/pylo-hooks";

export function ProjectList() {
  const { data, pagination, isLoading, error } = pylo.usePyloList('project', {
    select: { id: true, name: true, created_at: true },
    pagination: { page: 1, per_page: 20 },
    filter: {
      sortby: [{ field: 'created_at', order: 'desc' }],
    },
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data?.map((project) => (
        <div key={project.id}>{project.name}</div>
      ))}
      <p>Page {pagination?.current_page} of {pagination?.last_page}</p>
    </div>
  );
}
```

| Field        | Type                          | Description                         |
| ------------ | ----------------------------- | ----------------------------------- |
| `data`       | `T[] \| undefined`            | Entities matching the select        |
| `pagination` | `PaginationData \| undefined` | Pagination metadata                 |
| `isLoading`  | `boolean`                     | True while the initial request runs |
| `error`      | `Error \| null`               | Error if the request failed         |
| `refetch`    | `() => void`                  | Trigger a refetch                   |

### usePyloById

Disabled while `id` is null or undefined.

```tsx filename="components/project-detail.tsx"
'use client';

import { pylo } from "@/lib/pylo-hooks";

export function ProjectDetail({ projectId }: { projectId: string }) {
  const { data: project, isLoading } = pylo.usePyloById('project', projectId, {
    select: {
      id: true,
      name: true,
      customer: { select: { id: true, name: true } },
    },
  });

  if (isLoading) return <div>Loading...</div>;
  if (!project) return <div>Not found</div>;

  return (
    <div>
      <h1>{project.name}</h1>
      <p>Customer: {project.customer?.data.name}</p>
    </div>
  );
}
```

### usePyloInfiniteList

Infinite scrolling built on `useInfiniteQuery`.

```tsx filename="components/infinite-list.tsx"
'use client';

import { pylo } from "@/lib/pylo-hooks";

export function InfiniteProjectList() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
    pylo.usePyloInfiniteList('project', {
      perPage: 20,
      select: { id: true, name: true },
      filter: {
        sortby: [{ field: 'created_at', order: 'desc' }],
      },
    });

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      {data?.data.map((project) => (
        <div key={project.id}>{project.name}</div>
      ))}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? 'Loading...' : 'Load more'}
        </button>
      )}
    </div>
  );
}
```

## Select

You get back the fields you set to `true`, and the result type matches that selection.

```typescript filename="example.ts"
select: { id: true, name: true }
// -> { id: string; name: string | null }
```

## Relations

Include relations with a nested select:

```typescript filename="example.ts"
select: {
  id: true,
  // hasOne: pick specific fields
  customer: {
    select: { id: true, name: true },
  },
  // hasMany: with filter and pagination
  invoices: {
    select: { id: true, amount_net: true },
    pagination: { per_page: 5 },
    filter: {
      sortby: [{ field: 'created_at', order: 'desc' }],
    },
  },
}
```

Response shapes:

- **hasOne** (ManyToOne, OneToOne): `{ data: { ... } } | null`
- **hasMany** (OneToMany, ManyToMany): `{ data: [...], pagination: { ... } }`

## Error handling

The server client throws `PyloError`:

```typescript filename="example.ts"
import { PyloError } from '@pylo/nextjs';

try {
  const { data } = await pylo.project.list();
} catch (error) {
  if (error instanceof PyloError) {
    console.error(error.message);
    console.error(error.graphqlErrors);
  }
}
```

The hooks return an `error` state instead of throwing.

Next: [Filtering & Sorting](/data/filtering-and-sorting)
