Skip to main content
Composables are reusable functions that leverage Vue’s Composition API to encapsulate and share stateful logic. In Nuxt 3, composables placed in the composables/ directory are automatically imported throughout your application.

What Are Composables?

Composables are functions that:
  • Follow the useXxx naming convention
  • Can use Vue’s reactivity system (ref, reactive, computed)
  • Encapsulate reusable logic
  • Are automatically imported in Nuxt 3
Auto-import: All files in composables/ are automatically available without explicit imports. Just call useApi() or useServiceOrders() anywhere in your app.

useApi Composable

The useApi composable provides a configured Axios instance for making HTTP requests to the backend API.

Full Source Code

composables/useAPI.ts
import axios from 'axios'

export const useApi = () => {
  const config = useRuntimeConfig()
  const token = import.meta.client ? localStorage.getItem('access_token') : null
  const { public: { apiBaseUrl } } = useRuntimeConfig()

  const api = axios.create({
    baseURL: apiBaseUrl as string,
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      ...(token ? { Authorization: `Bearer ${token}` } : {})
    }
  })

  return api
}

Key Features

Reads the API base URL from Nuxt’s runtime config:
const { public: { apiBaseUrl } } = useRuntimeConfig()
The base URL is defined in nuxt.config.ts:
runtimeConfig: {
  public: {
    apiBaseUrl: 'http://localhost:8000/api/'
  }
}
Retrieves the authentication token from localStorage (only on client):
const token = import.meta.client ? localStorage.getItem('access_token') : null
Why check import.meta.client?
  • Prevents errors during server-side rendering
  • localStorage is only available in the browser
Automatically includes the Bearer token in all requests:
headers: {
  'Accept': 'application/json',
  'Content-Type': 'application/json',
  ...(token ? { Authorization: `Bearer ${token}` } : {})
}
If a token exists, adds: Authorization: Bearer <token>

Usage Examples

<script setup>
const api = useApi()
const form = ref({ email: '', password: '' })

const checkLogin = async () => {
    await api.post('/login', form.value).then(
        (response) => {
            localStorage.setItem('access_token', response.data.access_token)
            window.location.href = '/serviceorders'
        }).catch(
            (error) => {
                alert(`Validacion Incorrecta: ${error.response.data.message}`)
            })
}
</script>

useServiceOrders Composable

The useServiceOrders composable manages service order state and provides helper functions for working with orders.

Full Source Code

composables/useServiceOrders.ts
import { ref } from 'vue'
import type { ServiceOrder } from '~/types/ServiceOrder'

export const useServiceOrders = () => {
  const serviceOrders = ref<ServiceOrder[]>([
    {
      id: 1,
      number: 123456,
      type: "garantía",
      part_number: "ab123",
      serial_number: "6303934",
      description: "Lorem ipsum dolor sit amet...",
      solution: "lorem ipsum",
      company: { name: "Tech Solutions S.A.S", headquarters: "Bogota" },
      contacts: { name: "Angie", last_name: "Gonzales", gender: "Femenino", active: true, identification: 1101532654, charge: "admin", birthday: "09/12/2005" },
      mails: { mail: "[email protected]" },
      phones: { phone: 31356245120 },
      addresses: { address: "Crr 5 #23-67" },
      cities: { city: "Bogota" },
      equipment_types: { name: "Lenovo" },
      delivery_conditions: { description: "descripcion" }
    },
    // ... more mock data
  ])

  const selectedOrder = ref<ServiceOrder>(getEmptyOrder())

  function openModal(isNew: boolean, orderObject: any = null) {
    selectedOrder.value = isNew ? getEmptyOrder() : orderObject
  }

  function getEmptyOrder(): ServiceOrder {
    return {
      id: 0,
      number: 0,
      type: '',
      part_number: '',
      serial_number: '',
      description: '',
      solution: '',
      company: { name: '', headquarters: '' },
      contacts: { name: '', last_name: '', gender: '', active: true, identification: 0, charge: '', birthday: '' },
      mails: { mail: '' },
      phones: { phone: 0 },
      addresses: { address: '' },
      cities: { city: '' },
      equipment_types: { name: '' },
      delivery_conditions: { description: '' }
    }
  }

  return {
    serviceOrders,
    selectedOrder,
    openModal
  }
}

Key Features

Uses Vue’s ref() to create reactive state:
const serviceOrders = ref<ServiceOrder[]>([...])
const selectedOrder = ref<ServiceOrder>(getEmptyOrder())
  • serviceOrders: Array of all service orders
  • selectedOrder: Currently selected order for viewing/editing
openModal(isNew, orderObject)
  • Prepares the selected order for modal display
  • If isNew is true, creates an empty order
  • Otherwise, uses the provided order object
getEmptyOrder()
  • Returns a new empty ServiceOrder object
  • Used when creating new orders
  • Ensures all required fields have default values
Uses the ServiceOrder type from types/ServiceOrder.ts:
import type { ServiceOrder } from '~/types/ServiceOrder'
Provides type safety and autocomplete for order data.

Usage in Pages

pages/serviceorders/index.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useServiceOrders } from '@/composables/useServiceOrders'
import type { ServiceOrder } from '~/types/ServiceOrder'

const { serviceOrders, selectedOrder, openModal } = useServiceOrders()

const flags = ref({
    showServiceOrderModal: false,
    showOrderDetailModal: false,
    showSolutionModal: false
})

function editOrder(order: ServiceOrder) {
    selectedOrder.value = { ...order }
    flags.value.showServiceOrderModal = true
}

function OrderDetails(order: ServiceOrder) {
    selectedOrder.value = { ...order }
    flags.value.showOrderDetailModal = true
}

const api = useApi()
const orders = ref<ApiResponse<ServiceOrder[]>>([])

onMounted(async () => {
   orders.value = await getServiceOrders()
})

const getServiceOrders = async (): Promise<ApiResponse<ServiceOrder[]>> => {
    const response = await api.get('/service-orders')
    return response
}
</script>
How it works:
  1. Destructures reactive state and functions from the composable
  2. Uses selectedOrder to track which order is being viewed/edited
  3. Calls openModal() to prepare orders for display
  4. Fetches real data from API and merges with composable state

Creating Custom Composables

Follow this pattern to create your own composables:
composables/useExample.ts
import { ref, computed } from 'vue'

export const useExample = () => {
  // 1. Define reactive state
  const data = ref([])
  const loading = ref(false)
  const error = ref(null)

  // 2. Define computed properties
  const hasData = computed(() => data.value.length > 0)

  // 3. Define functions
  async function fetchData() {
    loading.value = true
    try {
      const api = useApi()
      const response = await api.get('/endpoint')
      data.value = response.data
    } catch (err) {
      error.value = err
    } finally {
      loading.value = false
    }
  }

  // 4. Return everything you want to expose
  return {
    data,
    loading,
    error,
    hasData,
    fetchData
  }
}

Best Practices

Naming Convention

Always prefix composables with use (e.g., useApi, useServiceOrders)

Single Responsibility

Each composable should focus on one specific area of functionality

Return Object

Return an object with all state and functions you want to expose

Type Safety

Use TypeScript types for better developer experience and fewer bugs

Composables vs Stores

FeatureComposablesPinia Stores
State SharingNew instance per callSingleton (shared state)
Best ForComponent-specific logicGlobal application state
ReactivityYesYes
Auto-importYes (Nuxt)Yes (with @pinia/nuxt)
ExampleuseApi(), useServiceOrders()useAuthStore()
When to use which?
  • Use composables for reusable logic that doesn’t need to be shared globally
  • Use stores for state that multiple components need to access and modify

Next Steps

API Integration

Learn how to make API calls and handle responses

State Management

Understand global state with Pinia stores

Build docs developers (and LLMs) love