Introduction
The aim of this post is to outline the possibilities of using generated models and services from a swagger.json file with the OpenAPI Generator and present how to connect them with the react-query library as well as provide an example of usage.
Agenda
- OpenAPI Generator
- Generation of models and services
- Creating wrappers for generated services
- Creating a custom hook with the react-query library for previously implemented wrappers
- Providing an example of usage
OpenApi Generator
Let's start by checking the documentation on website https://openapi-generator.tech/ where we should find a lot of useful information about features of this generator. In our example the typescript-axios client will be applied to generate our models and services. Another vital aspect is the installation process, so we have two opportunities to install this generator. The first way is a global installation which is covered by documentation and the other is a local installation in a project's dev-dependencies which is favoured by me.
Generation of models and services
Let's move on to the code. In the example I would like to use yarn package manager instead of npm.
Local installation
The first step is to navigate to the root directory of your project and run the below command which installs the generator in the project's dev-dependencies.
yarn add -D @openapitools/openapi-generator-cli
The second step is to create your own script in the package.json file which will be responsible for generating models and services from a local or a hosted swagger.json in the indicated directory.
"scripts": {
"model-generate": "yarn run openapi-generator-cli generate -i http://localhost:5263/swagger/v1/swagger.json -g typescript-axios -o ./src/api-types"
}
Let's take a closer look at the model-generate script. The script has many elements which should be explained, so let's break it down into smaller components:
- yarn run openapi-generator-cli generate starts the generation script
- -i http://localhost:5263/swagger/v1/swagger.json is a path to the swagger.json file. This parameter may be an url or a path to a file which is located locally in your pc. Besides I must stress the fact that if you use an url, it has to be hosted on http, because the generator have problems with https on mac os or linux and any solution which I have found on the Net didn't solve this problem. (NOTE: I don't use windows, so I don't know if available solutions in the Net can handle this difficulty)
- -g typescript-axios is the client which will be used in the generate process
- -o ./src/api-types is the path to the directory where the files will be generated
The generation
The first step is executing the generated script (in our case it is the model-generate), so we again have two options to do that. The first is to execute below command
yarn model-generate
and the other is to go to the package.json file and hover over the model-generate script. After a few seconds we will see the tooltip with two items:
- Run Script
- Debug Script
so we can click the Run Script option. The generated files should materialize in the indicated directory (in our case it will be the src/api-types directory).
The second step is removing unnecessary files and directory from the directory, so after that step we should have only five files in the directory. The files are api.ts, base.ts, common.ts, configuration.ts and index.ts, so let's describe these files:
- api.ts contains generated models and services
- base.ts defines base elements like api, path, etc.
- common.ts defines common functions
- configuration.ts defines configurations for generated services
- index.ts helps us to import files from the directory
Wrappers for generated services
In order to properly handle the generated services, we need to make a wrapper for them. In the example I will use the object-oriented services from the api.ts file. Futhermore, I will put out how these services might be handled using the generated configuration or the axios instance in which you can use the interceptor, so let's create these wrappers which will be implemented in the api-clients.ts file. The first wrapper implementation is presented below (using the generated configuration).
const TIME_OUT_TIME = 5000;
const basePath = "https://localhost:7254";
const baseHeaders = {
"Cache-Control": "no-store",
"Content-Type": "application/json",
};
const baseConfiguration = new Configuration({
basePath,
baseOptions: {
timeout: TIME_OUT_TIME,
headers: baseHeaders,
},
});
const bearerConfiguration = (apiKey?: string): Configuration =>
new Configuration({
basePath,
baseOptions: {
timeout: TIME_OUT_TIME,
headers: {
...baseHeaders,
Authorization: `Bearer ${apiKey}`,
},
},
});
export const commentApi = new CommentApi(baseConfiguration);
export const enumApi = (accessToken?: string) =>
new EnumApi(bearerConfiguration(accessToken));
The second wrapper implementation is presented below (using the axios instance).
const axiosInstanceWithToken = axios.create({
baseURL: "https://localhost:7254",
timeout: 5000,
headers: {
"Cache-Control": "no-store",
"Content-Type": "application/json",
},
});
axiosInstanceWithToken.interceptors.request.use(async (config) => {
// Read token from external storage (example function)
const accessToken = await readAccessToken();
if (accessToken) {
config.headers = {
...config.headers,
Authorization: `Bearer ${accessToken}`,
};
}
return config;
});
export const commentApi = new CommentApi(
undefined,
undefined,
axiosInstanceWithToken
);
export const enumApi = new EnumApi(
undefined,
undefined,
axiosInstanceWithToken
);
However it might be worth mixing the first wrapper with the second one and create the hybrid of these solutions for requests with authorization and without it.
const baseConfiguration = new Configuration({
basePath: "https://localhost:7254",
baseOptions: {
timeout: 5000,
headers: {
"Cache-Control": "no-store",
"Content-Type": "application/json",
},
},
});
const axiosInstanceWithToken = axios.create({
baseURL: "https://localhost:7254",
timeout: 5000,
headers: {
"Cache-Control": "no-store",
"Content-Type": "application/json",
},
});
axiosInstanceWithToken.interceptors.request.use(async (config) => {
const accessToken = await readAccessToken();
if (accessToken) {
config.headers = {
...config.headers,
Authorization: `Bearer ${accessToken}`,
};
}
return config;
});
export const enumApi = new EnumApi(baseConfiguration);
export const commentApi = new CommentApi(
undefined,
undefined,
axiosInstanceWithToken
);