axios-openapi
v0.0.2
Published
Type-safe OpenAPI wrapper for Axios
Downloads
330
Readme
axios-openapi
Type-safe OpenAPI wrapper for Axios
The package keeps the original Axios API while adding typed requests based on an OpenAPI paths type
Features
- Typed endpoint URLs
- Typed path, query and body parameters
- Typed response data
- Keeps regular Axios calls available
- Supports custom Axios instances and response types
- Optional React request hooks
- Endpoint path generator
- Axios and React are not bundled
Installation
npm install axios-openapi axiosFor React hooks:
npm install reactOpenAPI types
Generate TypeScript definitions from your OpenAPI schema, for example with openapi-typescript:
npx openapi-typescript ./openapi.json -o ./src/api.tsThe generated module should export a paths type:
import type { paths } from "./api";Creating a typed Axios instance
import axios from "axios";
import { createOpenApiAxios } from "axios-openapi";
import type { paths } from "./api";
const axiosInstance = createOpenApiAxios<paths>()(
axios.create({
baseURL: "https://api.example.com",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}),
);
export default axiosInstance;createOpenApiAxios() mutates and returns the supplied Axios instance. Existing interceptors, defaults and custom properties are preserved.
Requests
Assume the OpenAPI schema contains:
GET /users/{userId}
GET /users
POST /usersGET with path parameters
const response = await axiosInstance.get({
url: "/users/{userId}",
path: {
userId: "42",
},
});
console.log(response.data);The endpoint, path parameter names and response data are inferred from the OpenAPI schema.
GET with query parameters
const response = await axiosInstance.get({
url: "/users",
query: {
page: 1,
search: "Max",
},
});POST with a request body
const response = await axiosInstance.post({
url: "/users",
body: {
name: "Max",
email: "[email protected]",
},
});Combining path, query and body
const response = await axiosInstance.patch({
url: "/users/{userId}",
path: {
userId: "42",
},
query: {
notify: true,
},
body: {
name: "Updated name",
},
});Axios request config
Pass Axios config as the second argument:
const response = await axiosInstance.get(
{
url: "/users/{userId}",
path: {
userId: "42",
},
},
{
signal: controller.signal,
headers: {
"X-Request-ID": "request-1",
},
},
);Regular Axios calls
The original Axios signatures remain available:
const response = await axiosInstance.get<User>("/users/42");
await axiosInstance.post<CreateUserResponse, AxiosResponse<CreateUserResponse>, CreateUserBody>(
"/users",
{
name: "Max",
},
);This is useful for endpoints that are absent from the OpenAPI schema.
Custom Axios response type
Libraries such as axios-cache-interceptor may return an extended Axios response type.
import axios from "axios";
import {
setupCache,
type CacheAxiosResponse,
} from "axios-cache-interceptor";
import { createOpenApiAxios } from "axios-openapi";
import type { paths } from "./api";
const axiosInstance = createOpenApiAxios<
paths,
CacheAxiosResponse
>()(
setupCache(
axios.create({
baseURL: "https://api.example.com",
}),
),
);The custom response fields remain available while response.data is inferred from OpenAPI.
Axios interceptors
Configure interceptors normally:
axiosInstance.interceptors.request.use((config) => {
config.headers.Authorization = `Bearer ${getAccessToken()}`;
return config;
});
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
return Promise.reject(error);
},
);Endpoint path generator
Use createEndpointPathGenerator() when you need a URL without making a request:
import { createEndpointPathGenerator } from "axios-openapi";
import type { paths } from "./api";
const endpointPath = createEndpointPathGenerator<paths>();
const url = endpointPath("/users/{userId}", {
userId: "42",
});
// /users/42Endpoints without path parameters do not require a second argument:
const url = endpointPath("/users");Path values are URL-encoded.
React hooks
Import React-specific APIs from the /react entry point:
import { createOpenApiAxiosHook } from "axios-openapi/react";Create hooks from an already typed Axios instance:
import axiosInstance from "./axios";
import { createOpenApiAxiosHook } from "axios-openapi/react";
const useAxios = createOpenApiAxiosHook(axiosInstance);
export default useAxios;The returned object contains a hook for every supported HTTP method:
useAxios.get(...)
useAxios.post(...)
useAxios.put(...)
useAxios.patch(...)
useAxios.delete(...)
useAxios.head(...)
useAxios.options(...)Basic GET request
import { useEffect } from "react";
import useAxios from "./useAxios";
function UserProfile({ userId }: { userId: string }) {
const {
request,
response,
isLoading,
isError,
error,
} = useAxios.get("/users/{userId}");
useEffect(() => {
void request({
path: {
userId,
},
});
}, [request, userId]);
if (isLoading) return <p>Loading...</p>;
if (isError) return <p>{String(error)}</p>;
if (!response) return null;
return <p>{response.name}</p>;
}POST request
import useAxios from "./useAxios";
function CreateUserButton() {
const { request, isLoading } = useAxios.post("/users");
async function createUser() {
const result = await request({
body: {
name: "Max",
email: "[email protected]",
},
});
console.log(result.data);
}
return (
<button disabled={isLoading} onClick={() => void createUser()}>
Create user
</button>
);
}Base request config
Provide config shared by calls made through the hook:
const { request } = useAxios.get("/users", {
cache: { enabled: true }
});Additional config can be supplied when calling request():
await request(
{
query: {
page: 2,
},
},
{
cache: {
enabled: true,
},
},
);For an endpoint without path, query or body properties, pass config directly:
const { request } = useAxios.get("/health");
await request({
signal: controller.signal,
});Hook result
type OpenApiAxiosHookResult<Response, Request> = {
response: Response | undefined;
request: Request;
isLoading: boolean | undefined;
isError: boolean;
error: unknown;
};Starting a new request aborts the previous request created by the same hook instance. The active request is also aborted when the component unmounts.
Parameter serialization
Path parameters are URL-encoded:
{ userId: "a/b" } // a%2FbQuery values are serialized as follows:
- strings, numbers and booleans become normal query values;
- dates use ISO format;
- arrays are JSON-stringified;
- nested objects use bracket notation;
nullandundefinedare omitted.
Example:
{
query: {
page: 1,
filters: {
active: true,
},
ids: ["1", "2"],
},
}Produces:
?page=1&filters%5Bactive%5D=true&ids=%5B%221%22%2C%222%22%5DSupported methods
GET
POST
PUT
PATCH
DELETE
HEAD
OPTIONSRequest bodies are passed as the Axios data argument for POST, PUT and PATCH.
