Skip to content

docs: add api-requests guide #823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: great job with this file! It's concise and to the point :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
sidebar_position: 4
---

# Handling API Requests

Feature-Sliced Design provides a structured way to organize your API request logic.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: I don't think this statement is very accurate — FSD doesn't provide a way to organize API request logic. I'd say the more accurate description is "here's how to organize request logic with the whole FSD structure"

suggestion: maybe we don't even need this intro and can get straight to the first section?


## 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`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): perhaps it would be clearer if we split the axios and fetch examples into different codeblocks? To demonstrate that they are not meant to be used together


```ts title="shared/api/client.ts"
// Example using axios
import axios from 'axios';

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

// --- OR ---

// Example using fetch
export const fetchClient = {
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.
};

// Choose one client, for instance, you might export your preferred client as 'client':
// export const client = axiosClient;
// export const client = fetchClient;
```

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

```ts title="shared/api/endpoints/login.ts"
import { client } from '../client'; // Assuming 'client' is configured and exported

export interface LoginCredentials {
email: string;
password: string;
}
Comment on lines +69 to +72
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I believe it's very common nowadays to pair your backend DTO definitions with runtime data validation with Zod or the likes, perhaps we can add something about it here? Maybe a link to the relevant section of the Types guide will suffice


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 `shared/api`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: I suspect some people will also want guidance on where in shared/api to put the generated code. What was nice about the previous version on another page is that it specified a concrete example — shared/api/openapi

suggestion: let's give a specific location here as well, and if they don't like it, they can come up with their own

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I also like this sentence from the previous version:

Ideally, you should also include a README in that folder that describes what these files are, how to regenerate them, etc.

Could we add that here as well?


## 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: [where to store the request function][examples-api-requests].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: this link kinda feels patched on top of the text, it doesn't flow naturally, in my opinion.

suggestion: like I wrote in another comment, maybe we could give some clarification about the content of that link, how it's relevant here? Because the linked page is talking about API requests in general, and here we have a very particular API request, so we can offer more precise advice.

I see it as something like "As explained in the [guide for 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

Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,6 @@ Other packages simply don't have typings, and you might want to declare them as
declare module "use-react-screenshot";
```

## Auto-generation of types
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: I don't quite like the idea of removing this section entirely. If I was searching for autogeneration of types, I might look in Types or I might look in API requests.

suggestion: I'd prefer if both pages could pick the user up where they are and show them the way. Perhaps we could also do it in a more elaborate way than just having a link — perhaps we can offer some useful clarifications about the content of that link, i. e. how the content of the link applies to the problem at hand.

So basically I would suggest keeping the heading as is, giving a link to the guide, and also saying something like "if you want to auto-generate types from an OpenAPI schema, put them in shared/api/openapi. If you want to auto-generate types from a Figma icon library, put them in shared/ui.

In fact, maybe we can just paraphrase and not even bother linking, it's just one small paragraph


It's common to generate types from external sources, for example, generating backend types from an OpenAPI schema. In this case, create a dedicated place in your codebase for these types, like `shared/api/openapi`. Ideally, you should also include a README in that folder that describes what these files are, how to regenerate them, etc.

[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers
[ext-type-fest]: https://github.com/sindresorhus/type-fest
[ext-zod]: https://zod.dev
Expand Down