@habityzer/nuxt-openapi-composables
Nuxt OpenAPI Composables
Generate type-safe Nuxt composables from OpenAPI schemas. Perfect for consuming REST APIs in your Nuxt 3 & 4 applications with full TypeScript support and auto-completion.
Features
- ๐ Automatic Generation: Generate composables from OpenAPI 3.x schemas
- ๐ฏ Type-Safe: Full TypeScript support with optional type generation
- ๐ง Configurable: Customize naming, imports, and authentication
- ๐ฆ Multiple Modes: Use as CLI tool or Nuxt module
- ๐ Auth Ready: Built-in support for cookie-based authentication
- ๐จ Clean API: Intuitive method names following REST conventions
- ๐งช Well Tested: Comprehensive test coverage
Installation
# Using pnpm (recommended)
pnpm add -D @habityzer/nuxt-openapi-composables
# Using npm
npm install -D @habityzer/nuxt-openapi-composables
# Using yarn
yarn add -D @habityzer/nuxt-openapi-composables
Quick Start
1. Generate Composables
Generate composables from your OpenAPI schema:
npx nuxt-openapi-composables generate \
--schema ./schema/api.json \
--output ./app/composables/api \
--types \
--types-import '~/types/api' \
--api-prefix '/api/symfony'
This will generate:
useOpenApi.ts- Core API client composableuse{Resource}Api.ts- Resource-specific composables (e.g.,useUserWordsApi.ts)api.ts- TypeScript types (if--typesflag is used)
Add to your package.json:
{
"scripts": {
"generate:api": "nuxt-openapi-composables generate -s ./schema/api.json -o ./composables/api --types"
}
}
2. Configure Nuxt
Add API prefix configuration to your nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
apiPrefix: '/api/symfony' // Your API path prefix
}
}
})
Or use environment variable:
# .env
NUXT_PUBLIC_API_PREFIX=/api/symfony
3. Use Generated Composables
// pages/user-words.vue
<script setup lang="ts">
const { getUserWordsApi, postUserWordsBatchApi } = useUserWordsApi()
// Fetch all user words
const { data: words } = await getUserWordsApi()
// Create words in batch
await postUserWordsBatchApi({
body: {
words: [
{ word: 'ephemeral', tags: ['Lesson 1'] },
{ word: 'abandon', tags: ['Lesson 1'] }
]
}
})
</script>
CLI Options
generate Command
Generate composables from OpenAPI schema:
nuxt-openapi-composables generate [options]
| Option | Alias | Description | Default |
|---|---|---|---|
--schema <path> |
-s |
Path to OpenAPI schema file | ./schema/api.json |
--output <dir> |
-o |
Output directory for composables | ./app/composables/api |
--types |
-t |
Generate TypeScript types using openapi-typescript | false |
--types-output <path> |
Output path for TypeScript types | ./app/types/api.ts |
|
--types-import <path> |
Import path for types in useOpenApi.ts | ~/types/api |
|
--api-prefix <path> |
Default API path prefix (can be overridden at runtime) | (none) | |
--cookie <name> |
-c |
Cookie name for authentication (deprecated) | authToken |
Examples
Basic usage:
nuxt-openapi-composables generate -s ./schema/api.json
With TypeScript types:
nuxt-openapi-composables generate \
-s ./schema/api.json \
-o ./composables/api \
--types \
--types-output ./types/api.ts \
--types-import '~/types/api'
Complete example:
nuxt-openapi-composables generate \
--schema ./openapi.json \
--output ./app/composables/api \
--types \
--types-output ./app/types/api.ts \
--types-import '~/types/api'
Note: Configure your API prefix in
nuxt.config.tsinstead of passing--api-prefixvia CLI. See Configuration section.
Generated Method Naming
The generator creates intuitive method names based on REST conventions with an "Api" suffix to distinguish from store methods:
| Endpoint | HTTP Method | Generated Method Name |
|---|---|---|
/api/tasks |
GET | getTasksApi |
/api/tasks |
POST | postTasksApi |
/api/tasks/{id} |
GET | getTaskApi (singular) |
/api/tasks/{id} |
PATCH | patchTaskApi |
/api/tasks/{id} |
DELETE | deleteTaskApi |
/api/tasks/{id}/complete |
POST | postTaskCompleteApi |
/api/user-words/batch |
POST | postUserWordsBatchApi |
/api/user-words/random |
GET | getRandomUserWordsApi |
Why the "Api" suffix?
The Api suffix helps distinguish between API calls and Pinia store methods when working in stores:
// In your Pinia store
export const useUserWordsStore = defineStore('userWords', () => {
const { getUserWordsApi } = useUserWordsApi() // API call
const words = ref([])
// Store action - no naming conflict!
async function getUserWords() {
words.value = await getUserWordsApi()
}
return { words, getUserWords }
})
Method Parameters
Generated methods accept an optional parameters object:
interface ApiMethodParams {
params?: Record<string, string | number> // Path parameters
body?: any // Request body
query?: Record<string, any> // Query parameters
}
Example usage:
const { getTaskApi, patchTaskApi } = useTasksApi()
// GET /api/tasks/123
const task = await getTaskApi({
params: { id: 123 }
})
// PATCH /api/tasks/123 with query params
await patchTaskApi({
params: { id: 123 },
body: { title: 'Updated' },
query: { refresh: true }
})
Configuration
API Prefix Configuration
The generated useOpenApi.ts reads the API prefix from Nuxt's runtime config.
Recommended: Configure in nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
apiPrefix: '/api/symfony' // Your API prefix
}
}
})
Alternative: Environment Variable
# .env
NUXT_PUBLIC_API_PREFIX=/api/symfony
Optional: Set a Hardcoded Default (CLI)
nuxt-openapi-composables generate \
--api-prefix '/api/symfony'
Note: The
--api-prefixCLI option is optional and only injects a hardcoded default into the generated code. If you're configuring the prefix innuxt.config.ts, you don't need to pass it via CLI. The CLI option is mainly useful for non-Nuxt projects or as a fallback.
Priority Order: Runtime Config > Environment Variable > CLI Default > Empty String
Error Handling
All API errors are thrown with status and statusText properties (Nuxt 4.3+ / Web API naming). Handle them in your application:
const { getUserWordsApi } = useUserWordsApi()
try {
const words = await getUserWordsApi()
} catch (error) {
if (error.status === 401) {
// Handle authentication error
await navigateTo('/login')
} else if (error.status === 404) {
// Handle not found
console.error('Resource not found')
} else {
// Handle other errors
console.error('API Error:', error.statusText)
}
}
Custom Types Import Path
If your types are in a different location:
nuxt-openapi-composables generate \
--types-import '@/types/api'
Nuxt Module (Optional)
You can also use as a Nuxt module for auto-import support:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@habityzer/nuxt-openapi-composables'],
openapiComposables: {
schemaPath: './schema/api.json',
outputDir: './app/composables/api',
cookieName: 'authToken',
autoImport: true
}
})
Note: The module provides auto-import support but doesn't auto-generate composables. You still need to run the CLI command to generate/update composables.
Workflow Integration
Development Workflow
- Update API Schema: Get latest OpenAPI schema from your backend
- Generate Composables: Run the generation command
- Use in Components: Import and use generated composables
{
"scripts": {
"update:schema": "curl https://api.example.com/openapi.json > schema/api.json",
"generate:types": "openapi-typescript ./schema/api.json -o ./types/api.ts",
"generate:api": "pnpm generate:types && nuxt-openapi-composables generate -s ./schema/api.json -o ./composables/api --types-import '~/types/api'",
"sync:api": "pnpm update:schema && pnpm generate:api"
}
}
CI/CD Integration
# .github/workflows/api-sync.yml
name: Sync API Schema
on:
schedule:
- cron: '0 0 * * *' # Daily
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm sync:api
- uses: peter-evans/create-pull-request@v5
with:
title: 'chore: update API composables'
branch: 'api-sync'
Examples
Basic CRUD Operations
// pages/tasks/index.vue
<script setup lang="ts">
const { getTasksApi } = useTasksApi()
const { data: tasks, refresh } = await useAsyncData(
'tasks',
() => getTasksApi()
)
</script>
// pages/tasks/[id].vue
<script setup lang="ts">
const route = useRoute()
const { getTaskApi, deleteTaskApi } = useTasksApi()
const { data: task } = await useAsyncData(
'task',
() => getTaskApi({ params: { id: route.params.id } })
)
async function deleteTask() {
await deleteTaskApi({ params: { id: route.params.id } })
await navigateTo('/tasks')
}
</script>
With Query Parameters
const { getTasksApi } = useTasksApi()
// GET /api/tasks?status=active&page=1
const tasks = await getTasksApi({
query: {
status: 'active',
page: 1,
limit: 20
}
})
With Pinia Stores
// stores/tasks.ts
export const useTasksStore = defineStore('tasks', () => {
const { getTasksApi, postTasksApi, deleteTaskApi } = useTasksApi()
const tasks = ref([])
const loading = ref(false)
async function fetchTasks() {
loading.value = true
try {
tasks.value = await getTasksApi()
} catch (error) {
console.error('Failed to fetch tasks:', error)
} finally {
loading.value = false
}
}
async function createTask(title: string) {
const task = await postTasksApi({ body: { title } })
tasks.value.push(task)
return task
}
return { tasks, loading, fetchTasks, createTask }
})
With TypeScript Types
// Import generated types
import type { paths } from '~/types/api'
const { postTasksApi } = useTasksApi()
type TaskCreate = paths['/api/tasks']['post']['requestBody']['content']['application/json']
type Task = paths['/api/tasks']['post']['responses']['201']['content']['application/json']
const newTask: TaskCreate = {
title: 'New Task',
description: 'Task description',
status: 'pending'
}
const task: Task = await postTasksApi({
body: newTask
})
Migration Guide
From Manual API Calls
Before:
const authToken = useCookie('authToken')
const tasks = await $fetch('/api/tasks', {
headers: {
Authorization: `Bearer ${authToken.value}`
}
})
After:
const { getTasksApi } = useTasksApi()
const tasks = await getTasksApi()
Troubleshooting
Composables not found
Make sure to:
- Run the generation command
- Check the output directory exists
- Restart your Nuxt dev server
Types not working
- Ensure
--typesflag is used - Check TypeScript config includes the output path
- Restart your IDE
API prefix not working
- Verify runtime config is set in
nuxt.config.ts - Check environment variable
NUXT_PUBLIC_API_PREFIX - Regenerate composables if you changed the CLI default
- Restart your Nuxt dev server
Requests going to wrong URL
The final URL is: apiPrefix + endpoint
Example:
- API Prefix:
/api/symfony - Endpoint:
/api/user-words - Final URL:
/api/symfony/api/user-words
If your backend strips the prefix, adjust accordingly.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details
Credits
Created by Vazgen Manukyan
Related Projects
- openapi-typescript - TypeScript types from OpenAPI
- Nuxt - The Intuitive Vue Framework
Related Packages
Nuxt module for generating an OpenAPI SDK, composables, server routes, and headl...
A module for Nuxt.js to generate pages from OpenAPI specifications
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.4.5 | ^3.0 | MIT | |
| 1.4.4 | ^3.0 | MIT | |
| 1.4.3 | ^3.0 | MIT | |
| 1.4.2 | ^3.0 | MIT | |
| 1.4.1 | ^3.0 | MIT | |
| 1.4.0 | ^3.0 | MIT | |
| 1.3.0 | ^3.0 | MIT | |
| 1.2.0 | ^3.0 | MIT | |
| 1.1.0 | ^3.0 | MIT | |
| 1.0.2 | ^3.0 | MIT | |
| 1.0.1 | ^3.0 | MIT |