Generate API Call with Orval and Axios from OpenAPI Schema

Friday, November 14, 2025
This is a repost from my old blog. First posted in 1/20/2020.

I have a project with .NET Core 9.0 backend and React frontend. With the built-in OpenAPI support via Microsoft.AspNetCore.OpenApi, it makes it so much easier to generate OpenAPI documents. For more information, visit https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/overview?view=aspnetcore-9.0.


To automate the process, I want to generate the document after built. And that's pretty easy as well by following instructions in (Generate OpenAPI documents at build-time)https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-9.0&tabs=visual-studio%2Cvisual-studio-code#generate-openapi-documents-at-build-time article.


In my csproj file, I put the following entries, so the Open API documents can be exported to a common location outside of the backend project directory with name api.json.

<PropertyGroup>
  <OpenApiDocumentsDirectory>../../../openapi</OpenApiDocumentsDirectory>
  <OpenApiGenerateDocumentsOptions>--file-name api</OpenApiGenerateDocumentsOptions>
  ...
</PropertyGroup>

On the frontend, I would like to automatically ingest that document and automatically generate code to make the api call. That's where Orval (https://orval.dev/) comes in. Since I want to use custom Axios instance, I use the following guide: https://orval.dev/guides/custom-axios. My orval.config.js looks like the following:

import { defineConfig } from 'orval';

export default defineConfig({
  api: { //custom name, it can be any friendly name.
    input: '../openapi/api.json',
    output: {
      mode: 'single',
      target: 'src/api/api.ts',
      schemas: 'src/api/model',
      mock: false,
      override: {
        mutator: {
          path: './src/services/axios-service.ts',
          name: 'request'
        }
      }
    }
  }
});

The input is the path to the Open API document. Within output, mode: Single tells Orval to generate a single file for all calls (usually one function per endpoint): https://orval.dev/reference/configuration/output#value-single. And target is the output location and name of the generated code. While schemas will hold the generated models/DTOs that represents request and response data. I just turn mock to false since I don't use Faker at this time https://orval.dev/reference/configuration/output#mock.


The override is where the interesting thing happen. In my case, it basically says, use the request function in ./src/services/axios-service.ts to make the call.


My axios-service.ts contains the following:

export const request = <T>(
    config: AxiosRequestConfig,
    params?: Params
  ): Promise<T> => {
    axiosInstance.defaults.baseURL = import.meta.env.VITE_BASE_URL;

    const source = axios.CancelToken.source();
    const axiosConfig: AxiosRequestConfig = {
      ...config,
      cancelToken: source.token,
      headers: {
        Authorization: `Bearer ${params?.accessToken}`
      }
    };
    
    const promise = axiosInstance(axiosConfig).then(({ data }) => data);

    // @ts-ignore
    promise.cancel = () => {
      source.cancel('Query was cancelled');
    };

    return promise;
  };

This way, I use the BASE_URL specified in .env file so I can differentiate between local and remote environment. Also, I allow injection of access token, so my call can be authorized by the backend.


Orval will then generate something similar to the following calls in api.ts:

options?: SecondParameter<typeof request<UserResponse>>,) => {
      return request<UserResponse>(
      {url: `/users`, method: 'POST'
    },
      options);
    }
  
export const getCustomers = (
    
 options?: SecondParameter<typeof request<CustomersResponse[]>>,) => {
      return request<CustomersResponse[]>(
      {url: `/customers`, method: 'GET'
    },
      options);
    }
  
export const postCustomers = (
    customersRequest: CustomersRequest,
 options?: SecondParameter<typeof request<void>>,) => {
      return request<void>(
      {url: `/customers`, method: 'POST',
      headers: {'Content-Type': 'application/json', },
      data: customersRequest
    },
      options);
    }

In package.json, I added command to run orval during npm run dev and npm run build commands:

"scripts": { "dev": "orval && ...", "build": "orval && ...", ... }
::

That way, when I run the frontend locally, it will generate the code using the latest document. It will also generate when run within CI/CD pipeline. It saves a lot of time by automatically modify the frontend code when the backend api changes.