How to use the swagger.json file on the frontend

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

  1. OpenAPI Generator
  2. Generation of models and services
  3. Creating wrappers for generated services
  4. Creating a custom hook with the react-query library for previously implemented wrappers
  5. 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
);
Upgrade your tech game with us
Contact us

Integration with the react-query library

As you probably know react-query is a tremendous library for handling requests. Unfortunately, I don't go into technical aspect of this library in this post, but if you would like to boost your savvy you should check the react-query documentation https://tanstack.com/query/v4/docs/react/overview or series about this library on Dominic blog https://tkdodo.eu/blog/practical-react-query. Let's focus on the code again. In this example I use enums for react-query keys and custom hooks for a query and a mutation.

// Enums for react-query keys
export const enum QueryKeys {
  GetComments = "GetComments",
}

export const enum MutationKeys {
  CreateCommentMutation = "CreateCommentMutation",
}

// Custom hook for the example query
export function useQueryGetComments() {
  const query = useQuery(
    [QueryKeys.GetComments],
    async ({ signal }) => (await commentApi.commentGet({ signal })).data,
    {
      onError: (error) => {
        // example handling
        if (axios.isAxiosError(error)) {
          console.log(error.message);
        }
        // other guards and error handling
      },
    }
  );
  return query;
}

// Custom hook for the example mutation
export function useMutationCreateComment() {
  const queryClient = useQueryClient();

  const mutation = useMutation(
    (createCommentInput?: CreateCommentInput) =>
      commentApi.commentPost(createCommentInput),
    {
      mutationKey: [MutationKeys.CreateCommentMutation],
      onSuccess: () => queryClient.invalidateQueries([QueryKeys.GetComments]),
      onError: (error) => {
        // example handling
        if (axios.isAxiosError(error)) {
          console.log(error.message);
        }
        // other guards and error handling
      },
    }
  );
  return mutation;
}

Example

I would like to present an example using react-hook-form and yup for handling mutation and display comments from the example query. Unfortunately, again I won't go into handling forms in this post, so let's have a look at the form implementation.

// Handling form example with mutation
const commentFormSchema = Yup.object({
  title: Yup.string().required("Title is required."),
  message: Yup.string().required("Message is required."),
  date: Yup.string().required("Date is required."),
  author: Yup.string().required("Author is required."),
  type: Yup.mixed<CommentType>().required("Type is required."),
});

type CommentFormType = Yup.InferType<typeof commentFormSchema>;

export const FormContent = () => {
  const { data: commentTypesEnum = [], isLoading: commentTypesLoading } =
    useQueryEnumCommentTypes();
  // The custom hook for mutation
  const { mutate: createCommentMutation, isLoading: createCommentLoading } =
    useMutationCreateComment();

  const {
    register,
    handleSubmit,
    formState: { isValid, errors },
  } = useForm<CommentFormType>({
    resolver: yupResolver(commentFormSchema),
    defaultValues: {
      title: "",
      message: "",
      author: "",
      date: "",
      type: 0,
    },
    mode: "onBlur",
  });

  const onSubmit: SubmitHandler<CommentFormType> = (data) => {
    // Mutation from the custom hook
    createCommentMutation({
      title: data.title,
      message: data.message,
      date: data.date,
      author: data.author,
      type: +data.type as CommentType,
    });
  };

  return (
    <>
      <LoadingOverlay isLoading={commentTypesLoading || createCommentLoading} />
      <form
        onSubmit={handleSubmit(onSubmit)}
        className="max-w-md mx-auto mt-8 mb-0 space-y-4"
      >
        <FormInput
          register={register("title")}
          fieldName="title"
          label="Tilte"
          errorMessage={errors.title?.message}
        />
        <FormInput
          register={register("message")}
          fieldName="message"
          label="Message"
          errorMessage={errors.message?.message}
        />
        <FormInput
          register={register("date")}
          fieldName="date"
          type="date"
          label="Date"
          errorMessage={errors.date?.message}
        />
        <FormInput
          register={register("author")}
          fieldName="author"
          label="Author"
          errorMessage={errors.author?.message}
        />
        <FormSelectForEnumInput
          data={commentTypesEnum}
          register={register("type")}
          fieldName="type"
          label="Type"
        />
        <Button text="Add comment" type="submit" disabled={!isValid} />
      </form>
    </>
  );
};

// Display comments from the custom hook
export const CommentList = () => {
  const { data: comments = [], isLoading } = useQueryGetComments();

  return (
    <div className="flex flex-col w-full mx-auto px-16 py-10">
      <LoadingOverlay isLoading={isLoading} />
      {comments.map((comment) => (
        // My component for displaying comments
        <CommentCard key={comment.id} comment={comment} />
      ))}
    </div>
  );
};

To sum up, the presented solution might bring forward the early part of implementation when models and services have been altered by backend developers.

The repositories with examples are available on my github at https://github.com/DamZyl/example-front and https://github.com/DamZyl/example-api.

Related articles

What is enterprise resource planning software
Technology
Business
23/12/24

What is ERP? Your comprehensive software guide

Boost efficiency by integrating processes and managing operations seamlessly with ERP solutions.

Refactor or Rewrite - which is better for your web application?
Technology
13/03/24

Refactoring vs rewrite – which is better for your web application?

Plan to refresh your web app? You are keen to boost efficiency and safety. Check when to choose a code refactor and rewrite.

The journey from theory to practice in software engineering - part 2
Technology
08/03/24

The journey from theory to practice in software engineering - Part 2

Discover surprising applications of computer science theory: job interviews, day-to-day problem-solving, and innovations.

<Our latest articles>

Stay informed with our insightful blog posts

View all posts
Article cover image for Prepare for staff augmentation in 2025 [FREE CHECKLIST]
Business
Outsourcing
10/03/25

Prepare for staff augmentation in 2025 [FREE CHECKLIST]

Learn how to create an evergreen team augmentation strategy and get your copy of our free staff aug checklist!

IT staff augmentation company
Business
Outsourcing
09/03/25

10 red flags when choosing a staff augmentation company

IT staff augmentation is a game of strategy. Spot the 10 red flags early and make the right moves to win.

In-house vs outsourcing 
vs freelance software development
Business
Outsourcing
17/02/25

In-house vs outsourcing vs freelance software development – which option will drive your business forward?

Outsourcing vs in-house vs freelancer. Which one will bolster your business? The key pros and cons.

Upgrade your tech game with us
Contact us

Looking for the IT partner recognised for excellence?

We’ve earned industry-leading awards for delivering top-notch solutions across multiple sectors.

Let’s start your project