While working on the attraction team project using the Next.js App Router, I encountered some inconveniences, especially being familiar with React and Axios. These issues include:
- Lack of interceptor functionality in client components.
- Using res.json, JSON.stringify
- Handling of errors above status 400 using res.ok.
- Missing API methods like axios.post in the fetch API.
To address these inconveniences, EasyFetch was created as an extended fetch for Next.js App Router, making it more user-friendly for those accustomed to Axios.
pnpm add @woogie0303/easyfetch
yarn add @woogie0303/easyfetch
npm install @woogie0303/easyfetch
interface NextFetchRequestConfig {
revalidate?: number | false;
tags?: string[];
}
interface RequestInitWithNextConfig extends globalThis.RequestInit {
next?: NextFetchRequestConfig | undefined;
}
type EasyFetchResponse<T> = Omit<
Awaited<ReturnType<typeof fetch>>,
keyof Body | 'clone' | 'url'
> & {
body: T;
config: [string | URL, RequestInit | RequestInitWithNextConfig | undefined];
};
type EasyFetchRequestType = [
string | URL,
RequestInitWithNextConfig | undefined
];
const easy = easyFetch(defaultConfig);
defaultConfig?: {
baseUrl?: string | URL;
headers?: HeadersInit;
}
const easy = easyFetch({
baseUrl: 'https://attraction/',
headers: {
'Content-Type': 'multipart/form-data',
},
});
const request = new Request('https://hi');
easy.request(request);
easy.get('getUser');
easy.delete('deleteUser');
easy.patch('postUser', { userData: 'kang' });
easy.post('postUser', { userData: 'kang' });
easy.put('postUser', { userData: 'kang' });
<T>(url: string | URL, reqConfig?: Omit<RequestInitWithNextConfig, 'method'>) =>
Promise<EasyFetchResponse<T>>;
<T>(
url: string | URL,
reqBody?: object, // Request Body
reqConfig?: Omit<RequestInitWithNextConfig, 'method' | 'body'>
) => Promise<EasyFetchResponse<T>>;
<T>(request: RequestInfo | URL, requestInit?: RequestInitWithNextConfig) =>
Promise<EasyFetchResponse<T>>;
This feature allows you to add custom logic before a request is sent.
const easy = easyFetch();
easy.interceptor.request(onFulfilled, onReject);
(
onFulfilled?: (arg: EasyFetchRequestType)
=> | Promise<EasyFetchRequestType>
| EasyFetchRequestType,
onRejected?: (err: any) => any
): void
You can configure logic to handle responses before they are processed. For handling 400-level errors, use the EasyFetchResponse type assertion to handle server errors.
const easy = easyFetch();
easy.interceptor.response(onFulfilled, onReject);
(
onFulfilled?: (arg: EasyFetchResponse<any>)
=> | Promise<EasyFetchResponse<any>>
| EasyFetchResponse<any>,
onRejected?: (err: any) => any
): void
const easy = easyFetch();
easy.interceptor.response(
(res) => res,
async (err) => {
const error = err as EasyFetchResponse<ErrorType>;
if (error.status === 401) {
const { body } = await easy.get<Token>('https://google.co/getToken', {
...error.config[1],
});
const headers = new Headers(error.config[1]?.headers);
headers.set('Authorization', `Bearer ${body.accessToken}`);
return easy.request(error.config[0], {
...error.config[1],
headers,
});
}
throw err;
}
);
When handling errors, note that the Interceptor Response feature is not the same as transforming the response data. Returning an error config url as an argument helps maintain type consistency.
// Incorrect usage
const easy = easyFetch();
easy.interceptor.response(
(res) => res,
(err) => {
const serverError = err as EasyFetchResponse<ResponseType>;
return { data: 'Hi' };
}
);
const data = await easy.get<ResponseType>('https://sdf'); // return {data: 'Hi'}
// Correct usage
const easy = easyFetch();
easy.interceptor.response(
(res) => res,
(err) => {
const serverError = err as EasyFetchResponse<ResponseType>;
const [url, requestConfig] = serverError.config;
return easy.get(url, requestConfig);
}
);
const data = easy.get<ResponseType>('https://sdf'); // return ResponseType Data
MIT © DongWook Kang