How do I Call a Service API with Nuxt.js

Today i want to share my journey debugging and enhancing a Nuxt.js API service. I recently encountered a frustrating error while trying to call an API endpoint from my service file, and I’d like to explain how I solved it and expanded the functionality for a richer application experience.

Understanding the Error

When I first ran my application, I was greeted with the following error:

: services_ClientesService__WEBPACK_IMPORTED_MODULE_12_.default.list is not a function

This error puzzled me until I dug into the details. It turns out that the service was exported as a function factory rather than a plain object. Because of that, importing it gives you the factory function, not the object that contains the list method. When I tried to call .list() directly on the import, it failed. The solution was to instantiate the service by calling the factory function providing the configured $axios instance so that I could access its methods.

Explanation of the Code

The Service File (ClientesService.js)

Here’s what my service file looked like when I started:

// ClientesService.js in services folder
export default ($axios) => ({
list() {
return $axios.get('clientes')
},
})

What It Does:
This file exports a function that accepts an $axios instance as its argument. When I call this function and pass in $axios, it returns an object that has a list method. The list method in turn calls the /clientes API endpoint.

Why Export as a Function Factory:
Exporting it as a factory function allows me to inject dependencies—like $axios—into my service. This injection improves testability and configuration flexibility because I can use different Axios instances or mocks depending on the environment.

The Component File (e.g., medicos.vue)

In the component, my initial code was:

<script>
import ClientesService from '@/services/ClientesService'
export default {
name: 'Medicos',
data: () => ({
loading: false,
especialidades: []
}),
async mounted() {
await this.getEspecialidades()
},
methods: {
async getEspecialidades() {
this.loading = true
try {
// The error happens here because ClientesService is still a factory, not an object
const resp = await ClientesService.list()
this.especialidades = resp.data
} catch (error) {
console.log(error)
} finally {
this.loading = false
}
},
},
}
</script>

What’s Happening:
The component is trying to call ClientesService.list() directly. But since ClientesService is a factory function, what I need to do is instantiate it with $axios (usually available as this.$axios in a Nuxt context) before accessing the list method.

Fixing the Error

After identifying the problem, here’s how I fixed it in my component:

 getEspecialidades() {
this.loading = true
try {
// Instantiate the service using this.$axios
const clientesService = ClientesService(this.$axios)
// Now call the list method on the instantiated object
const resp = await clientesService.list()
this.especialidades = resp.data
} catch (error) {
console.log(error)
} finally {
this.loading = false
}
}

By instantiating the service with this.$axios, I could correctly access the .list() method, which resolved the error.

Adding More Practice Functionality

Once I had the basic API call working, I decided to expand the ClientesService with additional CRUD operations. This not only reinforced my understanding of service factories in Nuxt.js but also prepared the project for future scalability. Here’s the extended version of my service:

Extended Service File (ClientesService.js)

// ClientesService.js in services folder
export default ($axios) => ({
// List all clients
list() {
return $axios.get('clientes')
},

// Get a single client by ID
get(clientId) {
return $axios.get(`clientes/${clientId}`)
},

// Create a new client
create(clientData) {
return $axios.post('clientes', clientData)
},

// Update an existing client by ID
update(clientId, clientData) {
return $axios.put(`clientes/${clientId}`, clientData)
},

// Delete a client by ID
delete(clientId) {
return $axios.delete(`clientes/${clientId}`)
},
})

Using the Extended Service in a Component

I then integrated this extended functionality into a new component. Here’s how the component interacts with these methods:

<template>
<div>
<h1>Clientes</h1>
<div v-if="loading">Loading...</div>
<ul v-else>
<li v-for="cliente in clientes" :key="cliente.id">
{{ cliente.nombre }}
</li>
</ul>

<!-- Example buttons to demonstrate create, update, and delete -->
<button @click="createCliente">Create Cliente</button>
<button @click="updateCliente(1)">Update Cliente with ID 1</button>
<button @click="deleteCliente(1)">Delete Cliente with ID 1</button>
</div>
</template>

<script>
import ClientesService from '@/services/ClientesService'
export default {
name: 'ClientesComponent',
data: () => ({
loading: false,
clientes: [],
}),
mounted() {
this.fetchClientes()
},
methods: {
async fetchClientes() {
this.loading = true
try {
const clientesService = ClientesService(this.$axios)
const resp = await clientesService.list()
this.clientes = resp.data
} catch (error) {
console.error('Error fetching clients:', error)
} finally {
this.loading = false
}
},
async createCliente() {
try {
const clientesService = ClientesService(this.$axios)
const newCliente = { nombre: 'Nuevo Cliente' }
const resp = await clientesService.create(newCliente)
console.log('Cliente created:', resp.data)
this.fetchClientes() // Refresh list after creation
} catch (error) {
console.error('Error creating client:', error)
}
},
async updateCliente(clientId) {
try {
const clientesService = ClientesService(this.$axios)
const updatedData = { nombre: 'Cliente Actualizado' }
const resp = await clientesService.update(clientId, updatedData)
console.log('Cliente updated:', resp.data)
this.fetchClientes() // Refresh list after update
} catch (error) {
console.error('Error updating client:', error)
}
},
async deleteCliente(clientId) {
try {
const clientesService = ClientesService(this.$axios)
await clientesService.delete(clientId)
console.log('Cliente deleted')
this.fetchClientes() // Refresh list after deletion
} catch (error) {
console.error('Error deleting client:', error)
}
}
},
}
</script>

Key Insights:

  • Instantiation: I learned that instantiation is crucial—always call the factory function with this.$axios to get the service object before accessing its methods.
  • Modularity: Organizing my API calls into a dedicated service file keeps my codebase clean and maintainable.
  • Reusability: The service can be reused across different components simply by importing it and initializing with the current $axios instance.
  • Asynchronous Flow: Leveraging async/await helps manage the asynchronous nature of API calls and keeps the code readable, with clear handling of loading states.

The End

Working through this error and extending the service functionality reminded me of the power of dependency injection and the benefits of modular code. By abstracting API calls into dedicated service files, I can keep my components focused solely on UI logic while centralizing all backend communication. This separation of concerns not only simplifies debugging but also significantly improves code maintainability as the application scales. I encourage you to explore using service factories in your projects, and as you extend their functionality, you’ll find yourself equipped to handle more complex interactions and build robust, modular applications with ease.

Related blog posts