https://github.com/qualisero/openapi-endpoint
Exposes type-safe API endpoint functions to Vue components using openapi.json and TanStack
https://github.com/qualisero/openapi-endpoint
openapi rest-api tanstack-query vue vuejs
Last synced: 2 months ago
JSON representation
Exposes type-safe API endpoint functions to Vue components using openapi.json and TanStack
- Host: GitHub
- URL: https://github.com/qualisero/openapi-endpoint
- Owner: qualisero
- Created: 2025-10-06T05:42:40.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2025-12-21T12:46:48.000Z (3 months ago)
- Last Synced: 2025-12-23T03:57:12.377Z (3 months ago)
- Topics: openapi, rest-api, tanstack-query, vue, vuejs
- Language: TypeScript
- Homepage:
- Size: 517 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
README
# OpenApiEndpoint
[](https://badge.fury.io/js/@qualisero%2Fopenapi-endpoint)
[](https://github.com/qualisero/openapi-endpoint/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
[](https://bundlephobia.com/package/@qualisero/openapi-endpoint)
Turns your `openapi.json` into typesafe API composables using Vue Query (TanStack Query): guaranteeing that your frontend and backend share the same contract.
## Overview
Let's you get TanStack Vue Query composables that enforce consistency (name of endpoints, typing) with your API's `openapi.json` file:
```typescript
const { data, isLoading } = api.useQuery(OperationId.getPet, { petId: '123' })
const createPetMutation = api.useMutation(OperationId.createPet)
createPetMutation.mutate({ data: { name: 'Fluffy', species: 'cat' } })
```
## Installation
```bash
npm install @qualisero/openapi-endpoint
```
## Code Generation
This package includes a command-line tool to generate TypeScript types and operation definitions from your OpenAPI specification:
```bash
# Generate from local file
npx @qualisero/openapi-endpoint ./api/openapi.json ./src/generated
# Generate from remote URL
npx @qualisero/openapi-endpoint https://api.example.com/openapi.json ./src/api
```
This will generate two files in your specified output directory:
- `openapi-types.ts` - TypeScript type definitions for your API
- `api-operations.ts` - Operation definitions combining metadata and types
## Usage
### 1. Initialize the package
```typescript
// api/init.ts
import { useOpenApi } from '@qualisero/openapi-endpoint'
import axios from 'axios'
// Import your auto-generated operations (includes both metadata and types)
import { OperationId, openApiOperations, type OpenApiOperations } from './generated/api-operations'
// Create axios instance
const axiosInstance = axios.create({
baseURL: 'https://api.example.com',
})
// Initialize the package with the auto-generated operations
const api = useOpenApi({
operations: openApiOperations,
axios: axiosInstance,
})
// Export for use in other parts of your application
export { api, OperationId }
```
### 2. Use the API in your components
```typescript
// In your Vue components
import { api, OperationId } from './api/init'
// Use queries for GET operations
const { data: pets, isLoading } = api.useQuery(OperationId.listPets)
const { data: pet } = api.useQuery(OperationId.getPet, { petId: '123' })
// Use mutations for POST/PUT/PATCH/DELETE operations
const createPetMutation = api.useMutation(OperationId.createPet)
// Execute mutations
await createPetMutation.mutateAsync({
data: { name: 'Fluffy', species: 'cat' },
})
```
## Advanced Usage
### Reactive Query Parameters
The library supports type-safe, reactive query parameters that automatically trigger refetches when their values change:
```typescript
import { ref, computed } from 'vue'
import { api, OperationId } from './api/init'
// Static query parameters
const { data: pets } = api.useQuery(OperationId.listPets, {
queryParams: { limit: 10 },
})
// Results in: GET /pets?limit=10
// Reactive query parameters with computed
const limit = ref(10)
const status = ref<'available' | 'pending' | 'sold'>('available')
const petsQuery = api.useQuery(OperationId.listPets, {
queryParams: computed(() => ({
limit: limit.value,
status: status.value,
})),
})
// When limit or status changes, query automatically refetches
limit.value = 20
status.value = 'pending'
// Query refetches with: GET /pets?limit=20&status=pending
// Combine with path parameters
const userPetsQuery = api.useQuery(
OperationId.listUserPets,
computed(() => ({ userId: userId.value })),
{
queryParams: computed(() => ({
includeArchived: includeArchived.value,
})),
},
)
// Results in: GET /users/user-123/pets?includeArchived=false
```
**Key Features:**
- **Type-safe**: Query parameters are typed based on your OpenAPI specification
- **Reactive**: Supports `ref`, `computed`, and function-based values
- **Automatic refetch**: Changes to query params trigger automatic refetch via TanStack Query's key mechanism
- **Backward compatible**: Works alongside existing `axiosOptions.params`
### Automatic Operation Type Detection with `api.useEndpoint`
The `api.useEndpoint` method automatically detects whether an operation is a query (GET/HEAD/OPTIONS) or mutation (POST/PUT/PATCH/DELETE) based on the HTTP method defined in your OpenAPI specification:
```typescript
import { ref, computed } from 'vue'
import { api, OperationId } from './api/init'
// Automatically becomes a query for GET operations
const listEndpoint = api.useEndpoint(OperationId.listPets)
// TypeScript knows this has query properties like .data, .isLoading, .refetch()
// Automatically becomes a mutation for POST operations
const createEndpoint = api.useEndpoint(OperationId.createPet)
// TypeScript knows this has mutation properties like .mutate(), .mutateAsync()
// Use the endpoints according to their detected type
const petData = listEndpoint.data // Query data
await createEndpoint.mutateAsync({ data: { name: 'Fluffy' } }) // Mutation execution
```
### Automatic Cache Management and Refetching
By default, mutations automatically:
1. Update cache for matching queries with returned data
2. Invalidate them to trigger a reload
3. Invalidate matching list endpoints
```typescript
// Default behavior: automatic cache management
const createPet = api.useMutation(OperationId.createPet)
// No additional configuration needed - cache management is automatic
// Manual control over cache invalidation
const updatePet = api.useMutation(OperationId.updatePet, {
dontInvalidate: true, // Disable automatic invalidation
dontUpdateCache: true, // Disable automatic cache updates
invalidateOperations: [OperationId.listPets], // Manually specify operations to invalidate
})
// Refetch specific endpoints after mutation
const petListQuery = api.useQuery(OperationId.listPets)
const createPetWithRefetch = api.useMutation(OperationId.createPet, {
refetchEndpoints: [petListQuery], // Manually refetch these endpoints
})
```
### File Upload Support with Multipart/Form-Data
The library supports file uploads through endpoints that accept `multipart/form-data` content type. For these endpoints, you can pass either a `FormData` object or the schema-defined object structure:
```typescript
// Example file upload endpoint usage
async function uploadPetPicture(petId: string, file: File) {
const formData = new FormData()
formData.append('file', file)
const uploadMutation = api.useMutation(OperationId.uploadPetPic, { petId })
return uploadMutation.mutateAsync({
data: formData,
axiosOptions: {
headers: {
'Content-Type': 'multipart/form-data',
},
},
})
}
// Alternative: using the object structure (if your API supports binary strings)
async function uploadPetPictureAsString(petId: string, binaryData: string) {
const uploadMutation = api.useMutation(OperationId.uploadPetPic, { petId })
return uploadMutation.mutateAsync({
data: {
file: binaryData, // Binary data as string
},
})
}
// Complete example with error handling and cache invalidation
async function handleFileUpload(event: Event, petId: string) {
const files = (event.target as HTMLInputElement).files
if (!files || files.length === 0) return
const file = files[0]
const formData = new FormData()
formData.append('file', file)
const uploadMutation = api.useMutation(
OperationId.uploadPetPic,
{ petId },
{
invalidateOperations: [OperationId.getPet, OperationId.listPets],
onSuccess: (data) => {
console.log('Upload successful:', data)
},
onError: (error) => {
console.error('Upload failed:', error)
},
},
)
try {
await uploadMutation.mutateAsync({ data: formData })
} catch (error) {
console.error('Upload error:', error)
}
}
```
### Reactive Enabling/Disabling Based on Path Parameters
One powerful feature is chaining queries where one query provides the parameters for another:
```typescript
import { ref, computed } from 'vue'
// First query to get user information
const userQuery = api.useQuery(OperationId.getUser, { userId: 123 })
// Second query that depends on the first query's result
const userPetsQuery = api.useQuery(
OperationId.listUserPets,
computed(() => ({
userId: userQuery.data.value?.id, // Chain: use ID from first query
})),
)
// Reactive parameter example
const selectedPetId = ref(undefined)
// Query automatically enables/disables based on parameter availability
const petQuery = api.useQuery(
OperationId.getPet,
computed(() => ({ petId: selectedPetId.value })),
)
// Query is automatically disabled when petId is null/undefined
console.log(petQuery.isEnabled.value) // false when selectedPetId.value is null
// Enable the query by setting the parameter
selectedPetId.value = '123'
console.log(petQuery.isEnabled.value) // true when selectedPetId.value is set
// Complex conditional enabling
const userId = ref('user1')
const shouldFetchPets = ref(true)
const userPetsQuery = api.useQuery(
OperationId.listUserPets,
computed(() => ({ userId: userId.value })),
{
enabled: computed(
() => shouldFetchPets.value, // Additional business logic
),
},
)
```
### Reactive Query Parameters with Refs
```typescript
import { ref } from 'vue'
// Use reactive query parameters
const limit = ref(10)
const petsQuery = api.useQuery(OperationId.listPets, {
queryParams: { limit: limit.value },
})
// When limit changes, the query automatically refetches
limit.value = 20 // Query refetches with new parameter
// Results in: GET /pets?limit=20
```
## API Documentation
Full API documentation is available at [https://qualisero.github.io/openapi-endpoint/](https://qualisero.github.io/openapi-endpoint/). The documentation includes detailed information about all methods, types, and configuration options.
## Contributing
Contributions are welcome! Please read our contributing guidelines and ensure all tests pass.
## License
MIT