Skip to main content
The Service Orders Management System communicates with a backend API using Axios. This page explains how API integration works, including authentication, error handling, and common patterns.

API Setup

The application uses the useApi() composable to create configured Axios instances for API calls.

Axios Configuration

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 Configuration Elements

The API base URL is configured in nuxt.config.ts using runtime config:
nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      apiBaseUrl: 'http://localhost:8000/api/'
    }
  },
})
Benefits:
  • Environment-specific configuration
  • Can be overridden with environment variables
  • Centralized API endpoint management
Usage:
const { public: { apiBaseUrl } } = useRuntimeConfig()
Every request includes these headers:
headers: {
  'Accept': 'application/json',
  'Content-Type': 'application/json',
  ...(token ? { Authorization: `Bearer ${token}` } : {})
}
  • Accept: Tells the server we expect JSON responses
  • Content-Type: Indicates we’re sending JSON data
  • Authorization: Bearer token for authenticated requests
The token is retrieved from localStorage on the client:
const token = import.meta.client ? localStorage.getItem('access_token') : null
Why the client check?
  • Prevents errors during server-side rendering (SSR)
  • localStorage is only available in browsers
  • Returns null on server, ensuring SSR compatibility

Authentication Flow

Login Request

Here’s how the login process works in pages/index.vue:
pages/index.vue (login)
<script setup>
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import { useAuthStore } from '~/stores/auth';

const form = ref({
    email: '',
    password: ''
})

const api = useApi()

const checkLogin = async () => {
    validateForm()
    
    if (!formFlag.value) {
        return
    }
    
    await api.post('/login', form.value).then(
        (response) => {
            // Store the access token
            localStorage.setItem('access_token', response.data.access_token)
            // Redirect to service orders page
            window.location.href = '/serviceorders'
        }).catch(
            (error) => {
                // Handle login errors
                if (error.response.data.message) {
                    alert(`Validacion Incorrecta: ${error.response.data.message}`)
                }
                else {
                    alert(`Validacion Incorrecta`)
                }
                
                form.value.email=""
                form.value.password=""
        })
}
</script>
Flow:
  1. User submits email and password
  2. POST request to /login endpoint
  3. On success: Store token in localStorage and redirect
  4. On error: Display error message and clear form

Authenticated Requests

Once logged in, all subsequent requests automatically include the Bearer token:
const api = useApi()

// This request includes: Authorization: Bearer <token>
const response = await api.get('/service-orders')

Logout Request

layouts/default.vue (logout)
<script setup>
const api = useApi()

const logout = async () => {
    // Call logout endpoint
    await api.post('/logout')
    // Clear local token
    localStorage.removeItem('access_token')
    // Redirect to login
    window.location.href = '/'
}
</script>

Common API Patterns

Fetching Data

const api = useApi()
const orders = ref([])

const getServiceOrders = async () => {
    try {
        const response = await api.get('/service-orders')
        orders.value = response.data
    } catch (error) {
        console.error('Failed to fetch orders:', error)
    }
}

onMounted(async () => {
   await getServiceOrders()
})

Creating Data

const api = useApi()

const createOrder = async (orderData: ServiceOrder) => {
    try {
        const response = await api.post('/service-orders', orderData)
        return response.data
    } catch (error) {
        console.error('Failed to create order:', error)
        throw error
    }
}

Updating Data

const api = useApi()

const updateOrder = async (id: number, orderData: ServiceOrder) => {
    try {
        const response = await api.put(`/service-orders/${id}`, orderData)
        return response.data
    } catch (error) {
        console.error('Failed to update order:', error)
        throw error
    }
}

Deleting Data

DELETE Request
const api = useApi()

const deleteOrder = async (id: number) => {
    try {
        const response = await api.delete(`/service-orders/${id}`)
        return response.data
    } catch (error) {
        console.error('Failed to delete order:', error)
        throw error
    }
}

Error Handling Patterns

Basic Error Handling

const api = useApi()

const fetchData = async () => {
    try {
        const response = await api.get('/service-orders')
        return response.data
    } catch (error) {
        console.error('Error:', error)
        // Handle error appropriately
        alert('Failed to fetch data')
    }
}

Detailed Error Handling

const api = useApi()

const login = async (credentials) => {
    try {
        const response = await api.post('/login', credentials)
        return response.data
    } catch (error) {
        // Check if error has response from server
        if (error.response) {
            // Server responded with error
            const message = error.response.data.message || 'Login failed'
            alert(`Validation Error: ${message}`)
        } else if (error.request) {
            // Request made but no response received
            alert('No response from server. Please check your connection.')
        } else {
            // Error setting up request
            alert('An error occurred. Please try again.')
        }
        throw error
    }
}

Error Handling with State

<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else>
      <!-- Display data -->
    </div>
  </div>
</template>

<script setup>
const api = useApi()
const orders = ref([])
const loading = ref(false)
const error = ref(null)

const fetchOrders = async () => {
    loading.value = true
    error.value = null
    
    try {
        const response = await api.get('/service-orders')
        orders.value = response.data
    } catch (err) {
        error.value = err.message
    } finally {
        loading.value = false
    }
}

onMounted(() => {
    fetchOrders()
})
</script>

Response Structure

Successful Response

// API Response
{
  data: [
    {
      id: 1,
      number: 123456,
      type: "garantía",
      company: { name: "Tech Solutions" },
      // ... other fields
    }
  ]
}

// Accessing data
const response = await api.get('/service-orders')
const orders = response.data // Array of orders

Error Response

// API Error Response
{
  response: {
    status: 401,
    data: {
      message: "Invalid credentials"
    }
  }
}

// Handling error
try {
  await api.post('/login', credentials)
} catch (error) {
  console.log(error.response.status)        // 401
  console.log(error.response.data.message)  // "Invalid credentials"
}

TypeScript Integration

Define interfaces for API responses:
interface ApiResponse<T> {
  data: T;
}

const getServiceOrders = async (): Promise<ApiResponse<ServiceOrder[]>> => {
    const response = await api.get('/service-orders')
    return response
}

Best Practices

Always Handle Errors

Use try-catch blocks and provide meaningful error messages to users

Loading States

Show loading indicators during API calls for better UX

Reuse API Instance

Always use useApi() instead of creating new Axios instances

Type Your Responses

Use TypeScript interfaces for API responses to ensure type safety

Common Endpoints

Based on the codebase, here are the API endpoints in use:
MethodEndpointDescriptionAuth Required
POST/loginAuthenticate userNo
POST/logoutEnd user sessionYes
GET/service-ordersGet all service ordersYes
GET/service-orders/:idGet specific orderYes
POST/service-ordersCreate new orderYes
PUT/service-orders/:idUpdate entire orderYes
PATCH/service-orders/:idPartially update orderYes
DELETE/service-orders/:idDelete orderYes
All endpoints (except /login) require a valid Bearer token in the Authorization header.

Next Steps

Composables

Learn more about the useApi composable

State Management

Understand how authentication state is managed

Build docs developers (and LLMs) love