RTK Query : Génération automatique depuis Swagger/OpenAPI

2024-12-01

ReactRedux ToolkitTypeScriptAPI

Introduction

La synchronisation entre le frontend et le backend est un défi constant dans les applications modernes. Les contrats d’API évoluent, les types changent, et maintenir manuellement la couche API devient rapidement une source d’erreurs. RTK Query, combiné avec la génération automatique depuis OpenAPI/Swagger, offre une solution élégante à ce problème.

Dans cet article, nous allons explorer comment automatiser complètement la génération de notre couche API Redux Toolkit Query à partir de notre documentation Swagger backend.

Pourquoi automatiser la génération ?

Les avantages

  • Type safety garantie : Les types TypeScript sont générés directement depuis la spec OpenAPI
  • Synchronisation automatique : Chaque modification backend se reflète immédiatement côté frontend
  • Moins d’erreurs : Élimination des erreurs de typage manuel
  • Gain de temps : Plus besoin d’écrire manuellement les endpoints et types
  • Documentation vivante : Le code généré est toujours en phase avec la spec

Configuration initiale

Installation des dépendances

npm install @reduxjs/toolkit react-redux
npm install -D @rtk-query/codegen-openapi

Configuration du fichier de génération

Créez un fichier openapi-config.ts à la racine du projet :

import type { ConfigFile } from '@rtk-query/codegen-openapi';

const config: ConfigFile = {
  schemaFile: 'https://api.votre-backend.com/swagger.json',
  apiFile: './src/store/emptyApi.ts',
  apiImport: 'emptySplitApi',
  outputFile: './src/store/api.ts',
  exportName: 'api',
  hooks: true,
  tag: true,
};

export default config;

Création de l’API de base

Créez le fichier src/store/emptyApi.ts :

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const emptySplitApi = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: 'https://api.votre-backend.com' }),
  endpoints: () => ({}),
});

Génération de l’API

Commande de génération

Ajoutez dans votre package.json :

{
  "scripts": {
    "generate:api": "rtk-query-codegen-openapi openapi-config.ts"
  }
}

Exécutez la génération :

npm run generate:api

Résultat généré

Le fichier src/store/api.ts sera automatiquement créé avec tous vos endpoints :

import { emptySplitApi as api } from "./emptyApi";
const injectedRtkApi = api.injectEndpoints({
  endpoints: (build) => ({
    getUsers: build.query<GetUsersApiResponse, GetUsersApiArg>({
      query: (queryArg) => ({ url: `/users`, params: queryArg }),
    }),
    getUserById: build.query<GetUserByIdApiResponse, GetUserByIdApiArg>({
      query: (queryArg) => ({ url: `/users/${queryArg.id}` }),
    }),
    createUser: build.mutation<CreateUserApiResponse, CreateUserApiArg>({
      query: (queryArg) => ({ url: `/users`, method: 'POST', body: queryArg.body }),
    }),
    // ... tous les autres endpoints
  }),
  overrideExisting: false,
});

export { injectedRtkApi as api };
export const { useGetUsersQuery, useGetUserByIdQuery, useCreateUserMutation } = injectedRtkApi;

Utilisation dans les composants

Requête simple

import { useGetUsersQuery } from '@/store/api';

function UsersList() {
  const { data, isLoading, error } = useGetUsersQuery({ page: 1, limit: 10 });

  if (isLoading) return <div>Chargement...</div>;
  if (error) return <div>Erreur</div>;

  return (
    <ul>
      {data?.users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Mutation

import { useCreateUserMutation } from '@/store/api';

function CreateUserForm() {
  const [createUser, { isLoading }] = useCreateUserMutation();

  const handleSubmit = async (formData: UserInput) => {
    try {
      await createUser({ body: formData }).unwrap();
      // Success
    } catch (error) {
      // Error handling
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

Personnalisation avancée

Ajout de tags pour le cache invalidation

Modifiez openapi-config.ts pour inclure la logique de tags :

const config: ConfigFile = {
  // ... config précédente
  tag: true,
  endpointOverrides: [
    {
      pattern: 'getUsers',
      type: 'query',
      providesTags: ['User'],
    },
    {
      pattern: 'createUser',
      type: 'mutation',
      invalidatesTags: ['User'],
    },
  ],
};

Transformation des réponses

Vous pouvez étendre les endpoints générés :

import { api } from './api';

export const enhancedApi = api.enhanceEndpoints({
  endpoints: {
    getUsers: {
      transformResponse: (response) => {
        return response.users.map(user => ({
          ...user,
          fullName: `${user.firstName} ${user.lastName}`,
        }));
      },
    },
  },
});

Intégration dans le workflow

Script pre-commit

Ajoutez un hook Git pour générer automatiquement l’API :

{
  "husky": {
    "hooks": {
      "pre-commit": "npm run generate:api && git add src/store/api.ts"
    }
  }
}

CI/CD

Intégrez la vérification dans votre pipeline :

- name: Generate API
  run: npm run generate:api

- name: Check for changes
  run: |
    git diff --exit-code src/store/api.ts || \
    (echo "API not in sync with OpenAPI spec" && exit 1)

Conclusion

L’automatisation de la génération RTK Query depuis Swagger/OpenAPI transforme radicalement la façon dont nous maintenons la synchronisation frontend/backend. Cette approche élimine une classe entière d’erreurs, accélère le développement et garantit que notre code reste toujours en phase avec les contrats d’API.

Les bénéfices sont immédiats : type safety garantie, moins de code boilerplate, et un workflow de développement plus fluide. Si vous utilisez déjà RTK Query, l’adoption de la génération automatique est une évolution naturelle qui paiera rapidement ses dividendes.

Points clés à retenir :

  • La génération automatique élimine les erreurs de synchronisation
  • Le type safety TypeScript est garanti par la spec OpenAPI
  • L’intégration dans le workflow (hooks, CI/CD) assure la cohérence
  • La personnalisation reste possible via les overrides et extensions