ecyrbe / zodios

typescript http client and server with zod validation
https://www.zodios.org/
MIT License
1.71k stars 46 forks source link

Dynamic base URL using plugin #184

Closed astahmer closed 2 years ago

astahmer commented 2 years ago

Hey, I have a use-case where I need to inject a custom base URL depending on the request config, to do so i'm currently using a plugin creator (the baseUrl is an env var fetched at runtime and dynamic by country to be specific..)

it looks kind of like this:

const endpoints = makeApi([
  {
    method: 'get',
    path: '/v1/stores/:store_id',
    requestFormat: 'json',
    response: variables.d,
  },
  {
    method: 'get',
    path: '/v2/customer/:customer_id',
    requestFormat: 'json',
    response: variables.d,
  },
]);
// no baseUrl provided since it's only known at runtime
export const api = new Zodios(endpoints);

// ...
const createApiPlugin = (apiId: ApiId) => {
  return {
    name: 'apiPlugin',
    request: async (_endpoint, config) => {
      const state = xxx;
      const baseUrl = xxx[apiId];
      return { ...config, url: `${baseUrl}${config.url}` };
    }
  }
}

but then I get an error because the endpoint is not found in the response

would it be ok to allow overriding a baseUrl in the return of the plugin.request rather than overriding url completely ? i think this would solve my use-case and would not break anything

astahmer commented 2 years ago

found a workaround by passing the zodios api instance & overriding baseUrl directly on the axios instance


const createApiPlugin = (instance: ZodiosInstance<any>, apiId: ApiId) => {
  return {
    name: 'apiPlugin',
    request: async (_endpoint, config) => {
      const state = xxx;
      const baseUrl = xxx[apiId];
      instance.axios.defaults.baseURL = baseUrl;
      return config
    }
  }
}
ecyrbe commented 2 years ago

Hello alex,

I see, this is an oversight on my part, i desactivated baseURL from ZodiosConfig, but in reality it's still there :

export type AnyZodiosMethodOptions = Merge<
  {
    params?: Record<string, unknown>;
    queries?: Record<string, unknown>;
    headers?: Record<string, string>;
  },
  Omit<AxiosRequestConfig, "params" | "headers" | "baseURL" | "url" | "method">
>;

export type AnyZodiosRequestOptions = Merge<
  { method: Method; url: string },
  AnyZodiosMethodOptions
>;

So i can just remove the "baseURL" omit here.

So if you just change the config baseURL it will work, because it's just an axios config

i'll create a fast fix

ecyrbe commented 2 years ago

available on version 10.0.2 of @zodios/core. you can now just do :

const createApiPlugin = (apiId: ApiId) => {
  return {
    name: 'apiPlugin',
    request: async (_endpoint, config) => {
      const state = xxx;
      return { ...config, baseURL: state[apiId] };
    }
  }
}
ecyrbe commented 2 years ago

found a workaround by passing the zodios api instance & overriding baseUrl directly on the axios instance

const createApiPlugin = (instance: ZodiosInstance<any>, apiId: ApiId) => {
  return {
    name: 'apiPlugin',
    request: async (_endpoint, config) => {
      const state = xxx;
      const baseUrl = xxx[apiId];
      instance.axios.defaults.baseURL = baseUrl;
      return config
    }
  }
}

for information, this workaround can lead to nasty bug really difficult to track. indeed, since requests are async, one request could change the base URL and the another one change it again before request is sent. so you should switch to v10.0.2 and change the config per request. this is guaranteed to not have side effects

astahmer commented 2 years ago

ok perfect thanks again ! 🙏

UberMouse commented 2 years ago

Nice just wanted to do this myself, perfect timing :tada: