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.