Table of contents [Show]
- Introduction: Why Axios with Vue.js?
- Step 1: Installing Axios in a Vue Project
- Step 2: Setting Up Axios Globally
- Step 3: Making Your First GET Request
- Step 4: Sending Data with POST Requests
- Step 5: Handling Errors Gracefully
- Step 6: Building a Data-Driven User Interface
- Step 7: Using Axios Interceptors for Automation
- Step 8: Best Practices for Vue + Axios
- Step 9: Real-World Case Study – Building a Mini Blog
- Step 10: Adding a Loading Spinner and Error UI Component
- Conclusion
Introduction: Why Axios with Vue.js?
I still remember my first attempt at calling an API with raw JavaScript’s fetch(). It felt clunky, required extra error handling, and quickly got messy when chaining multiple requests. Enter Axios—a promise-based HTTP client that simplifies API calls, supports interceptors, and works beautifully with Vue.js.
In Vue applications, integrating Axios enables you to fetch data from REST APIs, send POST requests to servers, and build real-time, data-driven interfaces without breaking a sweat.
Step 1: Installing Axios in a Vue Project
Let’s kick things off by installing Axios. If you’re starting fresh with Vue 3, create a project using Vite or Vue CLI:
npm init vue@latest vue-axios-demo
cd vue-axios-demo
npm install
npm install axios
npm run dev
Congratulations! You’re now Axios-ready.
Step 2: Setting Up Axios Globally
While you can import Axios in every component, it’s cleaner to configure it globally. Create an axios.js helper inside a plugins folder:
// src/plugins/axios.js
import axios from "axios";
const apiClient = axios.create({
baseURL: "https://jsonplaceholder.typicode.com",
headers: {
"Content-Type": "application/json"
}
});
export default apiClient;
Now you can import apiClient anywhere in your project for consistent API calls.
Step 3: Making Your First GET Request
Let’s fetch posts from a sample API using Axios in a Vue component.
Posts.vue
<template>
<div>
<h2>Posts</h2>
<ul>
<li v-for="post in posts" :key="post.id">
{{ post.title }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import apiClient from "../plugins/axios";
const posts = ref([]);
onMounted(async () => {
try {
const response = await apiClient.get("/posts");
posts.value = response.data;
} catch (error) {
console.error("Error fetching posts:", error);
}
});
</script>
Here’s what happens:
onMountedtriggers the request when the component loads.- Axios retrieves posts, and we update the reactive
postsarray. - Vue automatically re-renders the list of posts.
Step 4: Sending Data with POST Requests
Fetching data is fun, but apps often need to send data too. Let’s create a form to add a new post.
NewPost.vue
<template>
<form @submit.prevent="createPost">
<input v-model="title" placeholder="Enter title" />
<textarea v-model="body" placeholder="Enter body"></textarea>
<button type="submit">Submit</button>
</form>
</template>
<script setup>
import { ref } from "vue";
import apiClient from "../plugins/axios";
const title = ref("");
const body = ref("");
const createPost = async () => {
try {
const response = await apiClient.post("/posts", {
title: title.value,
body: body.value,
userId: 1
});
console.log("Post created:", response.data);
} catch (error) {
console.error("Error creating post:", error);
}
};
</script>
With just a few lines of code, you’ve got form submissions working with Axios.
Step 5: Handling Errors Gracefully
Error handling is critical. Axios provides structured error responses, which you can catch and display.
try {
const response = await apiClient.get("/invalid-endpoint");
} catch (error) {
if (error.response) {
console.error("Server responded with:", error.response.data);
} else if (error.request) {
console.error("No response received:", error.request);
} else {
console.error("Error setting up request:", error.message);
}
}
Always communicate errors to users clearly—nobody likes a silent failure.
Step 6: Building a Data-Driven User Interface
Now let’s move beyond basic requests and build a UI that updates dynamically based on API responses.
UsersList.vue
<template>
<div>
<h2>Users</h2>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }} ({{ user.email }})
</li>
</ul>
<button @click="fetchUsers">Reload Users</button>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import apiClient from "../plugins/axios";
const users = ref([]);
const fetchUsers = async () => {
try {
const response = await apiClient.get("/users");
users.value = response.data;
} catch (error) {
console.error("Error fetching users:", error);
}
};
onMounted(fetchUsers);
</script>
This component displays users, and clicking “Reload Users” refetches fresh data—creating a fully data-driven experience.
Step 7: Using Axios Interceptors for Automation
Imagine you need to attach an authentication token to every request. Instead of repeating yourself, use interceptors:
apiClient.interceptors.request.use(config => {
const token = localStorage.getItem("authToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Now every Axios request automatically carries your token.
Step 8: Best Practices for Vue + Axios
- Centralize Axios configuration: Use a plugin or composable for consistency.
- Handle errors gracefully: Always display helpful messages.
- Keep UI responsive: Show loading spinners while waiting for API calls.
- Cache or paginate: Optimize large data sets with pagination or local caching.
- Secure sensitive data: Never hardcode API keys in frontend code.
Step 9: Real-World Case Study – Building a Mini Blog
Let’s combine everything into a blog application with posts and comments.
Blog.vue
<template>
<div>
<h1>Mini Blog</h1>
<div v-for="post in posts" :key="post.id" >
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
<button @click="loadComments(post.id)">View Comments</button>
<ul v-if="comments[post.id]">
<li v-for="comment in comments[post.id]" :key="comment.id">
{{ comment.name }}: {{ comment.body }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import apiClient from "../plugins/axios";
const posts = ref([]);
const comments = ref({});
const fetchPosts = async () => {
const response = await apiClient.get("/posts?_limit=5");
posts.value = response.data;
};
const loadComments = async (postId) => {
const response = await apiClient.get(`/posts/${postId}/comments`);
comments.value[postId] = response.data;
};
onMounted(fetchPosts);
</script>
<style scoped>
.post { margin-bottom: 20px; border-bottom: 1px solid #ccc; padding-bottom: 10px; }
</style>
This app loads posts and allows users to fetch comments dynamically—exactly what a real-world, data-driven Vue interface looks like.
Step 10: Adding a Loading Spinner and Error UI Component
When making API calls, users hate waiting without feedback. A loading spinner gives users a sense of progress, while a dedicated error component communicates what went wrong in a friendly way. Let’s make our app feel more polished and user-friendly.
LoadingSpinner.vue
This reusable component will show a simple animated spinner:
<template>
<div ></div>
</template>
<style scoped>
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #42b983;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 20px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
This component cleanly displays an error message.
Now let’s improve our UsersList.vue with loading and error states.
<template>
<div>
<h2>Users</h2>
<LoadingSpinner v-if="loading" />
<ErrorMessage v-if="error" :message="error" />
<ul v-if="!loading && !error">
<li v-for="user in users" :key="user.id">
{{ user.name }} ({{ user.email }})
</li>
</ul>
<button @click="fetchUsers">Reload Users</button>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import apiClient from "../plugins/axios";
import LoadingSpinner from "./LoadingSpinner.vue";
import ErrorMessage from "./ErrorMessage.vue";
const users = ref([]);
const loading = ref(false);
const error = ref(null);
const fetchUsers = async () => {
loading.value = true;
error.value = null;
try {
const response = await apiClient.get("/users");
users.value = response.data;
} catch (err) {
error.value = "Failed to load users. Please try again.";
} finally {
loading.value = false;
}
};
onMounted(fetchUsers);
</script>
Here’s how this improves UX:
- While waiting, the spinner is shown.
- If an error occurs, the red error box is displayed.
- On success, the users list renders normally.
Conclusion
We’ve covered everything from installing Axios to building full data-driven UIs in Vue. Along the way, you learned how to make GET and POST requests, handle errors, use interceptors, and follow best practices for building scalable, API-driven applications.You now have:
- API calls with Axios (GET, POST, error handling).
- Dynamic, data-driven Vue interfaces.
- Reusable components for loading and error states.
- Best practices like interceptors and clean code organization.
The next time someone asks, “How do you connect Vue.js to an API?”, you’ll confidently say: “With Axios, of course!” 🚀






