Skip to content
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

Axios: cannot customize the headers for requests #1752

Open
FLYSASA opened this issue Dec 18, 2024 · 3 comments
Open

Axios: cannot customize the headers for requests #1752

FLYSASA opened this issue Dec 18, 2024 · 3 comments
Labels
enhancement New feature or request

Comments

@FLYSASA
Copy link

FLYSASA commented Dec 18, 2024

I have generated my code, like this
image

but it seems I cannot configure headers for a specific API endpoint individually Content-Encoding': 'gzip. Is there any solution ?

My current temporary solution is:

export const useRecommendComponents = (queryOptions?: any) => {
  const mutation = usePostApiProjectsIdSimilarity({
    mutation: {
      ...queryOptions,
      mutationFn: async ({ id, data }) => {
        const paramsStr = JSON.stringify(data);
        const compressed = pako.gzip(encodeURIComponent(paramsStr));
        return axiosInstance({
          url: `/api/Projects/${id}/similarity`,
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Content-Encoding': 'gzip',
          },
          data: compressed,
        });
      },
    },
  });
  return mutation;
};
@melloware
Copy link
Collaborator

i use Axios Request Interceptors to add custom headers. You could check the path and add the right content encoding.

/**
 * Axios utlity class for adding token handling and Date handling to all request/responses.
 */
export default class AxiosInterceptors {
	// hold instances of interceptor by their unique URL
	static requestInterceptors = new Map<string, number>();
	static responseInterceptors = new Map<string, number>();

	/**
	 * Configures Axios request/reponse interceptors to add JWT token.
	 *
	 * @param {AxiosInstance} instance the Axios instance to remove interceptors
	 * @param {string} token the JWT token to add to all Axios requests
	 */
	static setupAxiosInstance = (instance: AxiosInstance, token: string) => {
		const appKey = instance.defaults.baseURL!;
		EnvUtils.debug(`Configuring Axios request/response interceptors for: ${appKey}`);
		// Axios request interceptor to add JWT token
		const tokenRequestInterceptor = instance.interceptors.request.use(
			(config) => {
				if (token) {
					const headers = config.headers || {};
					headers.Authorization = `Bearer ${token}`;
				}
				return config;
			},
			(error) => {
				return Promise.reject(error);
			}
		);
		EnvUtils.debug(`Axios Token Request Interceptor: ${tokenRequestInterceptor}`);
		AxiosInterceptors.requestInterceptors.set(appKey, tokenRequestInterceptor);

		// Axios response interceptor to translate String to Date
		const dateResponseInterceptor = instance.interceptors.response.use((originalResponse) => {
			const auditHeader = originalResponse.headers['audit-event-id'];
			if (auditHeader) {
				window.sessionStorage.setItem('audit-event-id', auditHeader);
				window.localStorage.setItem('audit-event-id', auditHeader);
			}
			FormatUtils.translateJsonDates(originalResponse.data);
			return originalResponse;
		});
		EnvUtils.debug(`Axios Date Response Interceptor: ${dateResponseInterceptor}`);
		AxiosInterceptors.responseInterceptors.set(appKey, dateResponseInterceptor);
	};

	/**
	 * Cleanup Axios on sign out of application.
	 *
	 * @param {AxiosInstance} instance the Axios instance to remove interceptors
	 */
	static teardownAxiosInstance = (instance: AxiosInstance) => {
		const appKey = instance.defaults.baseURL!;
		EnvUtils.warn(`Cleaning up Axios removing all interceptors for: ${appKey}`);
		instance.interceptors.request.clear();
		instance.interceptors.response.clear();
	};
}

@FLYSASA
Copy link
Author

FLYSASA commented Dec 19, 2024

Thank you very much for your answer. It's a good idea to add specific headers by checking the path in the axiosInstance interceptor. However, it would be even better if we could extend the orval-generated react-query request hooks to support passing axios configurations,

for usage example:

import {
  postApiProjectsIdSimilarity,
  usePostApiProjectsIdSimilarity,
} from '@/api/projects/projects';

export const useRecommendComponents = (queryOptions?: any) => {
  const mutation = usePostApiProjectsIdSimilarity({
    mutation: {
      ...queryOptions,
      mutationFn: async ({ id, data }) => {
        const paramsStr = JSON.stringify(data);
        const compressed = pako.gzip(encodeURIComponent(paramsStr));
        return postApiProjectsIdSimilarity(id, compressed);
      },
    },
    axiosConfig: {
      headers: {
        'Content-Type': 'application/json',
        'Content-Encoding': 'gzip',
      },
    },
  });
  return mutation;
};

for orval generate api:

image

@melloware melloware added the enhancement New feature or request label Dec 19, 2024
@melloware melloware changed the title cannot customize the headers for Axios requests Axios: cannot customize the headers for requests Dec 19, 2024
@maapteh
Copy link

maapteh commented Jan 30, 2025

With orval you can set your own client/fetch in generating options by overriding the mutator being used. For example we use timeout on some default operation ids, and we can add a header on any request/mutation from the orval generated hooks. Both can be set buildtime and used runtime.

Our custom Axios client looks like:

const baseUrl = 'https://foo.test'

type Arg = {
  url: string
  method: AxiosMethod
  params?: { [key: string]: string | string[] | number | boolean | undefined }
  data?: unknown
  headers?: Record<string, string>
  signal?: AbortSignal
  responseType?: string | Blob
}

type Options = {
  headers?: Record<string, string>
  timeout?: number
}

export const client = async <T>(
  { url, method = 'GET', params, data, headers, signal }: Arg,
  options?: Options
): Promise<T> => {
  const urlParams = params
    ? new URLSearchParams(
        Object.keys(params).reduce(
          (acc, key) => (params[key] === undefined ? { ...acc } : { ...acc, [key]: params[key] }),
          {}
        )
      )
    : ''

  return fetchNoCatch({
    url: `${baseUrl}${url}${params ? `?${urlParams}` : ''}`,
    method,
    headers: { ...headers, ...(options?.headers || {}) },
    ...(data ? { body: JSON.stringify(data) } : {}),
    signal,
    timeout: options?.timeout,
  })
}

where fetchNoCatch is just the axios request. Hope this helps.

I now have these options everywhere:

type Options = {
  headers?: Record<string, string>
  timeout?: number
}

Now you can do

useGeneratedHook({ query: {}, request: { headers: {'foo':'bar'}}})

in your Orval config you can also now pass these two Options, for example based on operation id from your open api spec:

override: {
    mutator: {
      path: '../mutator/client.ts',
      name: 'client',
    },
    operations: {
      getFooUnreachable: {
        requestOptions: {
          timeout: 30000,
        },
      },
    },
...

now in the generated hook this will be added standard, same you can do for headers :)

so option to keep it dynamic (consumer hook), or in the already generated version (which you can still overwrite, so can see it as a default... :)).

generated code with orval:

export const getFooUnreachable = (options?: SecondParameter<typeof client>, signal?: AbortSignal) => {
  return client<number[]>(
    { url: `/snip/unreachable`, method: 'GET', signal },
    { timeout: 30000, ...options }
  )
}

examples consumer:

useGetFooUnreachable({ request: { timeout: 10_000 } })
useGetFooUnreachable({ query: { gcTime: 0 }, request: { headers:  { 'foo': 'bar' } })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants