npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@habityzer/nuxt-openapi-composables

v1.4.2

Published

Generate type-safe Nuxt 3 & 4 composables from OpenAPI schemas

Readme

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 statusCode and statusMessage properties. Handle them in your application:

const { getUserWordsApi } = useUserWordsApi()

try {
  const words = await getUserWordsApi()
} catch (error) {
  if (error.statusCode === 401) {
    // Handle authentication error
    await navigateTo('/login')
  } else if (error.statusCode === 404) {
    // Handle not found
    console.error('Resource not found')
  } else {
    // Handle other errors
    console.error('API Error:', error.statusMessage)
  }
}

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 Projects