@habityzer/nuxt-openapi-composables

Generate type-safe Nuxt 3 & 4 composables from OpenAPI schemas
1,549
Install
npm install @habityzer/nuxt-openapi-composables
Latest Version:1.4.5
Vue:3
License:MIT
Last Updated:Oct 26, 2026
Links: GitHub  ·  npm
Maintainer: vazgen

Nuxt OpenAPI Composables

npm version License: MIT

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 composable
  • use{Resource}Api.ts - Resource-specific composables (e.g., useUserWordsApi.ts)
  • api.ts - TypeScript types (if --types flag 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.ts instead of passing --api-prefix via 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-prefix CLI option is optional and only injects a hardcoded default into the generated code. If you're configuring the prefix in nuxt.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

  1. Update API Schema: Get latest OpenAPI schema from your backend
  2. Generate Composables: Run the generation command
  3. 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:

  1. Run the generation command
  2. Check the output directory exists
  3. Restart your Nuxt dev server

Types not working

  1. Ensure --types flag is used
  2. Check TypeScript config includes the output path
  3. Restart your IDE

API prefix not working

  1. Verify runtime config is set in nuxt.config.ts
  2. Check environment variable NUXT_PUBLIC_API_PREFIX
  3. Regenerate composables if you changed the CLI default
  4. 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 Packages

nuxt-openapi-hyperfetch

Nuxt module for generating an OpenAPI SDK, composables, server routes, and headl...

4,669
nuxt-open-fetch

Generate zero-overhead, 100% typed OpenAPI clients for Nuxt.

379,588 259
nuxt-openapi-docs-module

A module for Nuxt.js to generate pages from OpenAPI specifications

14,276 37
nuxt-yup

yup implementation for nuxt

2,981 4

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