-
Notifications
You must be signed in to change notification settings - Fork 201
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
base: master
Are you sure you want to change the base?
Changes from all commits
0584a4c
32c7349
c44be41
838bfe8
1d6b202
46d68e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`. | ||
illright marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: I suspect some people will also want guidance on where in suggestion: let's give a specific location here as well, and if they don't like it, they can come up with their own There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: I also like this sentence from the previous version:
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 |
---|---|---|
|
@@ -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]. | ||
illright marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
### Two-factor authentication | ||
|
||
|
@@ -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 |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 | ||
|
There was a problem hiding this comment.
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 :)