openapi-ai
v1.0.3
Published
This library generates React forms from an OpenAPI JSON file using AI. It's designed to speed up your development workflow by automatically scaffolding UI components and data logic from your API definitions.
Readme
🧠 Openapi AI
This library generates React forms from an OpenAPI JSON file using AI. It's designed to speed up your development workflow by automatically scaffolding UI components and data logic from your API definitions.
🚀 Features
Generate React Forms from OpenAPI definitions
Customizable file paths and form names
Smart generation using AI with editable prompts
Fine-tune generation using advanced configuration
Installation
npm install -D openapi-ai⚙️ Props
In the root directory of your project, create a file named openapi-ai.json and include the following properties to customize the output results according to your requirements:
| Prop | Type | Description | Example |
| -------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| gemini_LLM_options | object | Configuration object for Gemini LLM integration. This enables intelligent suggestions, auto-generations, or enhancements using Google's Gemini model. Default: { model: "gemini-2.5-flash", temperature: 0 }Important: You must include your apiKey in this object to authenticate requests to the Gemini API. To generate an API key, visit Google AI Console. | { model: "gemini-2.0-pro", temperature: 0.7, apiKey: "YOUR_API_KEY" } |
| openapiPath | string | Specify your input schema in JSON or YAML format. You can provide: A local path: ./path/to/schema.json or A remote URL: https://api.test.com/swagger/v1/swagger.json | ./schemas/openapi.json or https://api.test.com/swagger.json |
| formOutputPath | string | Path where the generated form will be created | 'src/components/pages-components' |
| formName | string | Name of the form to generate. If null or an empty string, nothing will be generated | 'EmployeeForm' |
| formApiRoutes | string[] | API routes for add and update operations. | ['/Employee/Add', '/Employee/Update'] |
| librariesToUse | string[] | react libraries to use in generated components. Default is ['@mui/material', 'react-hook-form', 'yup', 'notistack', 'react-i18next']. | ['tailwind', 'next-translate', 'lucide-react'] |
💡 Note: The gemini_LLM_options must include a valid apiKey in order to interact with the Gemini language model. This is essential for authentication and request execution. To create your own API key, visit Google AI Studio and follow the instructions to generate an API key.
For more Gemini LLM options and parameter configurations, refer to the official LangChain documentation: 👉 Langchain
How to Get openapiPath
The openapiPath field should point to your OpenAPI (Swagger) specification file. This file describes your backend API in a machine-readable format, typically in JSON or YAML.
Here are common way to obtain it:
From Swagger UI (Public URL)
If your backend provides Swagger UI (often at /swagger or /swagger/index.html), you can usually find the JSON spec by Looking for URLs ending in:
/swagger/v1/swagger.json/v1/api-docs/openapi.json
Example:
"openapiPath": "https://api.yourdomain.com/swagger/v1/swagger.json"🧠 Advanced Prompt Customization
🔧 Global Prompt Override
inside your openapi-ai.json file you can use formPrompt prop to override the entire AI prompt used for generation.
🎯 Partial Prompt Customization
You can guide the AI by specifying examples or preferred structures:
| Prop | Type | Description |
| ------------------------ | ---------- | ----------------------------------------------- |
| exampleFormName | string | Form name example to help guide generation |
| exampleFormApiRoutes | string[] | API route examples to help structure the output |
| exampleFormContentPath | string | Example input path to guide file structure |
openapi-ai.json Configuration Example
This configuration file defines how to generate React forms from your OpenAPI specification using AI assistance.
Example
{
"gemini_LLM_options": {
"apiKey": "YOUR_API_KEY",
"model": "gemini-2.5-flash",
"temperature": 0
},
"openapiPath": "https://api.test.com/swagger/v1/swagger.json",
"formOutputPath": "src/forms",
"formName": "EmployeeForm",
"formApiRoutes": ["/Employee/Add", "/Employee/Update"],
"librariesToUse": [
"@mui/material",
"react-hook-form",
"yup",
"notistack",
"react-i18next"
],
"exampleFormName": "CityForm",
"exampleFormApiRoutes": ["/Country/AddCity", "/Country/UpdateCity"],
"exampleFormContentPath": "src/forms/CityForm.form.tsx"
}🛠️ Usage
- To generate form component from your OpenAPI specification, run:
npx openapi-ai🧾 Example Output
Below is a sample form generated by the library using the OpenAPI definition and configured props:
import * as Yup from "yup";
import { useSnackbar } from "notistack";
import { useForm } from "react-hook-form";
import { FormProvider } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { getErrorMessage } from "utils/helpers";
import React, { useMemo, useState } from "react";
import locationApi from "@apis/location/location.api";
import { yupResolver } from "@hookform/resolvers/yup";
import locationQueries from "@apis/location/location.queries";
import RHFTextField from "components/hook-form/rhf-text-field";
import RHFAutocomplete from "components/hook-form/rhf-autocomplete";
import {
ICity,
IAddCityPayload,
IUpdateCityPayload,
} from "@apis/location/location.interface";
import LoadingButton from "@mui/lab/LoadingButton/LoadingButton";
import { Box, Stack, Switch, FormControlLabel } from "@mui/material";
// ----------------------------------------------------------------------
type Props = {
editItem?: ICity;
/**
* Optional countryId to pre-select and disable the country field.
* Useful when adding a city from a country's detail page.
*/
countryId?: string;
};
// ----------------------------------------------------------------------
function CityForm({ editItem, countryId }: Props) {
const { t } = useTranslation();
const { enqueueSnackbar } = useSnackbar();
const [countrySearch, setCountrySearch] = useState<string>();
const {
data: countries,
isLoading: countriesLoading,
fetchNextPage: countriesFetchNextPage,
isFetchingNextPage: countriesIsFetchingNextPage,
} = locationQueries.useGetAllCountryInfiniteQuery(
{
queryParams: {
search: countrySearch,
isAvailable: true,
},
},
{
select: (result) =>
result?.pages.flatMap((page) => page?.data?.data || []) || [],
}
);
const validationSchema = Yup.object().shape({
name: Yup.string().required(t("required")),
nameEn: Yup.string().required(t("required")),
countryId: Yup.string().required(t("required")),
});
const defaultValues: IAddCityPayload = useMemo(
() => ({
id: editItem?.id || undefined,
name: editItem?.name || "",
nameEn: editItem?.nameEn || "",
countryId: editItem?.country?.id || countryId || "",
isAvailable: editItem?.isAvailable ?? true,
}),
[editItem, countryId]
);
const methods = useForm({
resolver: yupResolver(validationSchema),
defaultValues,
});
const {
reset,
handleSubmit,
register,
formState: { isSubmitting },
} = methods;
const onSubmit = handleSubmit(async (data: IAddCityPayload) => {
try {
if (editItem?.id) {
const payloadData: IUpdateCityPayload = {
...data,
id: editItem.id,
};
await locationApi.updateCity(payloadData);
} else {
await locationApi.addCity(data);
}
reset(data);
enqueueSnackbar(editItem ? t("update-success") : t("create-success"));
} catch (error) {
enqueueSnackbar(getErrorMessage(error), { variant: "error" });
}
});
return (
<FormProvider methods={methods}>
<form onSubmit={onSubmit}>
<Stack spacing={3}>
<Box
rowGap={3}
columnGap={2}
display="grid"
gridTemplateColumns={{
xs: "repeat(1, 1fr)",
sm: "repeat(2, 1fr)",
}}
sx={{ p: 3 }}
>
<RHFTextField name="name" label={t("cities.name")} required />
<RHFTextField name="nameEn" label={t("cities.nameEn")} required />
<RHFAutocomplete
name="countryId"
label={t("cities.country")}
options={countries || []}
getOptionLabel={(option) => option.name || ""}
isOptionEqualToValue={(option, value) => option.id === value}
loading={countriesLoading}
onInputChange={(_, value) => setCountrySearch(value)}
required
disabled={!!countryId} // Disable if countryId is passed as a prop
onReachEnd={countriesFetchNextPage}
isFetchingNextPage={countriesIsFetchingNextPage}
/>
<FormControlLabel
control={
<Switch
defaultChecked={defaultValues.isAvailable}
{...register("isAvailable")}
/>
}
label={t("available")}
sx={{ width: "fit-content", gridColumn: "1 / -1" }} // Span full width
/>
</Box>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<LoadingButton
type="submit"
variant="contained"
loading={isSubmitting}
>
{!editItem ? t("cities.create-city") : t("save-changes")}
</LoadingButton>
</Box>
</Stack>
</form>
</FormProvider>
);
}
// ----------------------------------------------------------------------
export default CityForm;
// ----------------------------------------------------------------------📁 Output Structure
When provided with the appropriate configuration, this tool will generate:
<formOutputPath>
└── <formName>.form.tsx