Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
sidebar_position: 4
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Handling API Requests

## Shared API Requests

Start by placing common API request logic in the `shared/api` directory. This makes it easy to reuse requests across your application and helps with faster prototyping. For many projects, this is all you'll need for API calls.

A typical file structure would be:
- 📂 shared
- 📂 api
- 📄 client.ts
- 📄 index.ts
- 📂 endpoints
- 📄 login.ts

The `client.ts` file centralizes your HTTP request setup. It wraps your chosen method (like `fetch()` or an `axios` instance) and handles common configurations, such as:

- Backend base URL.
- Default headers (e.g., for authentication).
- Data serialization.

Here are examples for `axios` and `fetch`:

<Tabs>

<TabItem value="axios" label="Axios">
```ts title="shared/api/client.ts"
// Example using axios
import axios from 'axios';

export const client = axios.create({
baseURL: 'https://your-api-domain.com/api/',
timeout: 5000,
headers: { 'X-Custom-Header': 'my-custom-value' }
});
```
</TabItem>

<TabItem value="fetch" label="Fetch">
```ts title="shared/api/client.ts"
export const client = {
async post(endpoint: string, body: any, options?: RequestInit) {
const response = await fetch(`https://your-api-domain.com/api${endpoint}`, {
method: 'POST',
body: JSON.stringify(body),
...options,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'my-custom-value',
...options?.headers,
},
});
return response.json();
}
// ... other methods like put, delete, etc.
};
```
</TabItem>

</Tabs>

Organize your individual API request functions in `shared/api/endpoints`, grouping them by the API endpoint.

:::note

To keep examples focused, we omit form interaction and validation. For details on libraries like Zod or Valibot, refer to the [Type Validation and Schemas](/docs/guides/examples/types#type-validation-schemas-and-zod) article.

:::

```ts title="shared/api/endpoints/login.ts"
import { client } from '../client';

export interface LoginCredentials {
email: string;
password: string;
}

export function login(credentials: LoginCredentials) {
return client.post('/login', credentials);
}
```
Use an `index.ts` file in `shared/api` to export your request functions.

```ts title="shared/api/index.ts"
export { client } from './client'; // If you want to export the client itself
export { login } from './endpoints/login';
export type { LoginCredentials } from './endpoints/login';
```

## Slice-Specific API Requests

If an API request is only used by a specific slice (like a single page or feature) and won't be reused, place it in the api segment of that slice. This keeps slice-specific logic neatly contained.

- 📂 pages
- 📂 login
- 📄 index.ts
- 📂 api
- 📄 login.ts
- 📂 ui
- 📄 LoginPage.tsx

```ts title="pages/login/api/login.ts"
import { client } from 'shared/api';

interface LoginCredentials {
email: string;
password: string;
}

export function login(credentials: LoginCredentials) {
return client.post('/login', credentials);
}
```

You don't need to export `login()` function in the page's public API, because it's unlikely that any other place in the app will need this request.

:::note

Avoid placing API calls and response types in the `entities` layer prematurely. Backend responses may differ from what your frontend entities need. API logic in `shared/api` or a slice's `api` segment allows you to transform data appropriately, keeping entities focused on frontend concerns.

:::

## Using Client Generators

If your backend has an OpenAPI specification, tools like [orval](https://orval.dev/) or [openapi-typescript](https://openapi-ts.dev/) can generate API types and request functions for you. Place the generated code in, for example, `shared/api/openapi`. Make sure to include `README.md` to document what those types are, and how to generate them.

## Integrating with Server State Libraries

When using server state libraries like [TanStack Query (React Query)](https://tanstack.com/query/latest) or [Pinia Colada](https://pinia-colada.esm.dev/) you might need to share types or cache keys between slices. Use the `shared` layer for things like:

- API data types
- Cache keys
- Common query/mutation options
Original file line number Diff line number Diff line change
Expand Up @@ -90,60 +90,7 @@ export function RegisterPage() {

## How to send credentials to the backend

Create a function that makes a request to your backend's login endpoint. This function can either be called directly in the component code using a mutation library (e.g. TanStack Query), or it can be called as a side effect in a state manager.

### Where to store the request function

There are two places you can put this function: in `shared/api`, or in the `api` segment of the page.

#### In `shared/api`

This approach goes well with when you put all your API requests in `shared/api`, grouped by endpoint, for example. The file structure might look like this:

- 📂 shared
- 📂 api
- 📂 endpoints
- 📄 login.ts
- other endpoint functions…
- 📄 client.ts
- 📄 index.ts

The `📄 client.ts` file contains a wrapper around your request-making primitive (for example, `fetch()`). This wrapper would know about the base URL of your backend, set necessary headers, serialize data correctly, etc.

```ts title="shared/api/endpoints/login.ts"
import { POST } from "../client";

export function login({ email, password }: { email: string, password: string }) {
return POST("/login", { email, password });
}
```

```ts title="shared/api/index.ts"
export { login } from "./endpoints/login";
```

#### In the `api` segment of the page

If you don't keep all your requests in one place, consider stashing the login request in the `api` segment of the login page.

- 📂 pages
- 📂 login
- 📂 api
- 📄 login.ts
- 📂 ui
- 📄 LoginPage.tsx
- 📄 index.ts
- other pages…

```ts title="pages/login/api/login.ts"
import { POST } from "shared/api";

export function login({ email, password }: { email: string, password: string }) {
return POST("/login", { email, password });
}
```

You don't have to export the `login()` function in the page's public API, because it's unlikely that any other place in the app will need this request.
Create a function that makes a request to your backend's login endpoint. This function can either be called directly in the component code using a mutation library (e.g. TanStack Query), or it can be called as a side effect in a state manager. As explained in the [guide for API requests][examples-api-requests], you can put your request either in `shared/api` or in the `api` segment of your login page.

### Two-factor authentication

Expand Down Expand Up @@ -220,6 +167,7 @@ Don't forget to build failsafes for when a request to log out fails, or a reques

[tutorial-authentication]: /docs/get-started/tutorial#authentication
[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers
[examples-api-requests]: /docs/guides/examples/api-requests
[ext-remix]: https://remix.run
[ext-zod]: https://zod.dev