Vue.js HTTP Requests
Most real applications fetch data from a server — user profiles, product lists, news articles. Vue does not have a built-in HTTP client, but it works seamlessly with the browser's native Fetch API and the popular Axios library.
Where to Make HTTP Requests in Vue
HTTP requests are side effects. The right place to trigger them depends on your scenario:
- On component load — use the
mounted()lifecycle hook (Options API) oronMounted()(Composition API) - When data changes — use a watcher
- When a user acts — call from a method triggered by a click or form submit
Diagram: When to Fetch
Page loads → mounted() → fetch initial data User types in search box → watcher → fetch search results User clicks "Save" → method → POST request User clicks page 2 → method → fetch page 2 data
Using the Fetch API
The Fetch API is built into every modern browser. No installation needed. It returns Promises.
GET Request — Loading Data
<div id="app">
<p v-if="loading">Loading...</p>
<p v-if="error">{{ error }}</p>
<ul v-if="!loading">
<li v-for="user in users" :key="user.id">
{{ user.name }} — {{ user.email }}
</li>
</ul>
</div>
<script>
Vue.createApp({
data() {
return {
users: [],
loading: false,
error: null
};
},
mounted() {
this.fetchUsers();
},
methods: {
async fetchUsers() {
this.loading = true;
this.error = null;
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) throw new Error("Server error: " + response.status);
this.users = await response.json();
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
}
}
}).mount("#app");
</script>
Diagram: GET Request Flow
Component mounts
│
▼
fetchUsers() called
loading = true → "Loading..." shown on page
│
▼
fetch("https://...") sends HTTP GET
│
Success │ Failure
│ └──▶ error = "Server error" → error message shown
▼
response.json() parses the data
users = [ { id:1, name:"Alice" }, ... ]
loading = false
│
▼
Vue renders the user list on screen
POST Request — Sending Data
methods: {
async createPost(title, body) {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, body, userId: 1 })
});
const newPost = await response.json();
console.log("Created:", newPost);
} catch (err) {
console.error("Failed to create post:", err);
}
}
}
Installing and Using Axios
Axios is a popular HTTP library that simplifies requests with cleaner syntax, automatic JSON parsing, and better error handling.
npm install axios
Axios vs Fetch — Key Differences
┌─────────────────────────┬─────────────────┬─────────────────┐ │ Feature │ Fetch │ Axios │ ├─────────────────────────┼─────────────────┼─────────────────┤ │ Built-in browser │ Yes │ No (install) │ │ Auto JSON parse │ No (manual) │ Yes │ │ HTTP error detection │ No (manual) │ Yes (auto) │ │ Request cancellation │ Manual │ Built-in │ │ Interceptors │ No │ Yes │ │ Shorter syntax │ Moderate │ Shorter │ └─────────────────────────┴─────────────────┴─────────────────┘
Axios GET Request
<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";
const posts = ref([]);
const loading = ref(false);
const error = ref(null);
async function fetchPosts() {
loading.value = true;
error.value = null;
try {
const response = await axios.get("https://jsonplaceholder.typicode.com/posts?_limit=5");
posts.value = response.data; // Axios parses JSON automatically
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
onMounted(fetchPosts);
</script>
<template>
<p v-if="loading">Fetching posts...</p>
<p v-if="error" style="color:red">{{ error }}</p>
<article v-for="post in posts" :key="post.id">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
</article>
</template>
Axios POST, PUT, DELETE
import axios from "axios";
const BASE = "https://jsonplaceholder.typicode.com";
// POST — create a new resource
async function createItem(data) {
const response = await axios.post(BASE + "/posts", data);
return response.data;
}
// PUT — replace a resource completely
async function updateItem(id, data) {
const response = await axios.put(BASE + "/posts/" + id, data);
return response.data;
}
// PATCH — update specific fields
async function patchItem(id, fields) {
const response = await axios.patch(BASE + "/posts/" + id, fields);
return response.data;
}
// DELETE — remove a resource
async function deleteItem(id) {
await axios.delete(BASE + "/posts/" + id);
}
Diagram: HTTP Methods and Their Purpose
GET /posts → Read all posts GET /posts/1 → Read post with id 1 POST /posts → Create a new post PUT /posts/1 → Replace post 1 entirely PATCH /posts/1 → Update specific fields of post 1 DELETE /posts/1 → Delete post 1
Axios Instance — Reusable Base Configuration
Create an Axios instance with a base URL and default headers. Every request made through the instance automatically includes those settings.
// src/api/http.js
import axios from "axios";
const http = axios.create({
baseURL: "https://api.yoursite.com/v1",
timeout: 10000,
headers: {
"Content-Type": "application/json"
}
});
// Add auth token to every request automatically
http.interceptors.request.use((config) => {
const token = localStorage.getItem("authToken");
if (token) config.headers.Authorization = "Bearer " + token;
return config;
});
export default http;
// In any component — no need to repeat base URL or headers
import http from "../api/http";
const users = await http.get("/users"); // calls api.yoursite.com/v1/users
const user = await http.post("/users", { name: "Alice" });
Loading and Error States Pattern
Every fetch operation should track three states: loading, success data, and error. This gives the user feedback at every stage.
Diagram: Three-State Request Pattern
┌─────────────────────────────────────────────────┐
│ Request has not started yet │
│ loading = false, data = [], error = null │
└─────────────────────────────────────────────────┘
│ fetch starts
▼
┌─────────────────────────────────────────────────┐
│ Request in progress │
│ loading = true → show spinner or "Loading..." │
└─────────────────────────────────────────────────┘
│
Success ─────┤───── Failure
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────────────┐
│ loading = false │ │ loading = false │
│ data = [...] │ │ error = "Network error" │
│ Show the data │ │ Show error message │
└─────────────────┘ └─────────────────────────┘
Composable for Data Fetching
Extract fetch logic into a reusable composable so any component can use it without duplicating code.
// src/composables/useFetch.js
import { ref } from "vue";
export function useFetch(url) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const res = await fetch(url);
if (!res.ok) throw new Error("HTTP error " + res.status);
data.value = await res.json();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
}
fetchData();
return { data, loading, error, refetch: fetchData };
}
<!-- Any component -->
<script setup>
import { useFetch } from "../composables/useFetch";
const { data: users, loading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
</script>
<template>
<p v-if="loading">Loading...</p>
<p v-if="error">{{ error }}</p>
<ul v-if="users">
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</template>
Summary
Make HTTP requests in Vue using the browser's Fetch API or the Axios library. Trigger requests in mounted() for on-load data, in watchers for reactive data changes, or in methods for user-triggered actions. Always track loading, data, and error states to give users clear feedback. Create an Axios instance with base URL and interceptors to avoid repeating configuration across components. Extract fetch logic into a composable to reuse it across the application without duplicating code.
