API Integration
Learn how to integrate Vue.js applications with external APIs. This guide covers making HTTP requests, handling responses, and managing API data effectively.
Setting Up API Integration
Installing Axios
Axios is the most popular HTTP client for JavaScript applications:
npm install axiosCreating an API Service
Organize your API calls in a dedicated service file:
// services/api.js
import axios from 'axios'
// Create axios instance with base configuration
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// Request interceptor for adding auth token
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('authToken')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// Response interceptor for handling errors
api.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
if (error.response?.status === 401) {
// Handle unauthorized access
localStorage.removeItem('authToken')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
export default apiMaking API Calls in Components
Basic GET Request
<!-- components/UserList.vue -->
<template>
<div class="user-list">
<h2>Users</h2>
<div v-if="loading" class="loading">
Loading users...
</div>
<div v-else-if="error" class="error">
{{ error }}
</div>
<div v-else class="users">
<div v-for="user in users" :key="user.id" class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<p>Role: {{ user.role }}</p>
</div>
</div>
<button @click="fetchUsers" :disabled="loading">
{{ loading ? 'Loading...' : 'Refresh Users' }}
</button>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import api from '@/services/api'
const users = ref([])
const loading = ref(false)
const error = ref(null)
const fetchUsers = async () => {
loading.value = true
error.value = null
try {
const response = await api.get('/users')
users.value = response.data
} catch (err) {
error.value = 'Failed to fetch users. Please try again.'
console.error('Error fetching users:', err)
} finally {
loading.value = false
}
}
onMounted(() => {
fetchUsers()
})
</script>
<style scoped>
.user-list {
padding: 20px;
}
.loading, .error {
text-align: center;
padding: 20px;
color: #666;
}
.error {
color: #dc3545;
}
.users {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.user-card {
padding: 15px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #f9f9f9;
}
.user-card h3 {
margin: 0 0 10px 0;
color: #42b883;
}
button {
padding: 10px 20px;
background-color: #42b883;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
</style>POST Request with Form Data
<!-- components/CreateUser.vue -->
<template>
<div class="create-user">
<h2>Create New User</h2>
<form @submit.prevent="createUser" class="user-form">
<div class="form-group">
<label for="name">Name:</label>
<input
id="name"
v-model="formData.name"
type="text"
required
/>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input
id="email"
v-model="formData.email"
type="email"
required
/>
</div>
<div class="form-group">
<label for="role">Role:</label>
<select id="role" v-model="formData.role" required>
<option value="">Select a role</option>
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="moderator">Moderator</option>
</select>
</div>
<button type="submit" :disabled="submitting">
{{ submitting ? 'Creating...' : 'Create User' }}
</button>
<div v-if="success" class="success">
User created successfully!
</div>
<div v-if="error" class="error">
{{ error }}
</div>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import api from '@/services/api'
const formData = ref({
name: '',
email: '',
role: ''
})
const submitting = ref(false)
const success = ref(false)
const error = ref(null)
const createUser = async () => {
submitting.value = true
success.value = false
error.value = null
try {
const response = await api.post('/users', formData.value)
success.value = true
// Reset form
formData.value = {
name: '',
email: '',
role: ''
}
// Emit event to parent component
emit('user-created', response.data)
} catch (err) {
error.value = 'Failed to create user. Please try again.'
console.error('Error creating user:', err)
} finally {
submitting.value = false
}
}
const emit = defineEmits(['user-created'])
</script>
<style scoped>
.create-user {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.user-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 5px;
font-weight: bold;
}
input, select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 10px 20px;
background-color: #42b883;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.success {
margin-top: 10px;
padding: 10px;
background-color: #d4edda;
color: #155724;
border-radius: 4px;
text-align: center;
}
.error {
margin-top: 10px;
padding: 10px;
background-color: #f8d7da;
color: #721c24;
border-radius: 4px;
text-align: center;
}
</style>Advanced API Patterns
Composable for API Calls
Create reusable composables for common API operations:
// composables/useApi.js
import { ref } from 'vue'
import api from '@/services/api'
export function useApi() {
const loading = ref(false)
const error = ref(null)
const execute = async (apiCall) => {
loading.value = true
error.value = null
try {
const result = await apiCall()
return result
} catch (err) {
error.value = err.message || 'An error occurred'
throw err
} finally {
loading.value = false
}
}
return {
loading,
error,
execute
}
}
// Specific composable for users
export function useUsers() {
const { loading, error, execute } = useApi()
const users = ref([])
const fetchUsers = async () => {
const result = await execute(() => api.get('/users'))
users.value = result.data
return result
}
const createUser = async (userData) => {
const result = await execute(() => api.post('/users', userData))
return result
}
const updateUser = async (id, userData) => {
const result = await execute(() => api.put(`/users/${id}`, userData))
return result
}
const deleteUser = async (id) => {
const result = await execute(() => api.delete(`/users/${id}`))
return result
}
return {
users,
loading,
error,
fetchUsers,
createUser,
updateUser,
deleteUser
}
}Using the Composable
<!-- components/UserManager.vue -->
<template>
<div class="user-manager">
<h2>User Manager</h2>
<button @click="loadUsers" :disabled="loading">
{{ loading ? 'Loading...' : 'Load Users' }}
</button>
<div v-if="error" class="error">
{{ error }}
</div>
<div class="users-grid">
<div v-for="user in users" :key="user.id" class="user-item">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button @click="deleteUser(user.id)" class="delete-btn">
Delete
</button>
</div>
</div>
</div>
</template>
<script setup>
import { useUsers } from '@/composables/useUsers'
const { users, loading, error, fetchUsers, deleteUser } = useUsers()
const loadUsers = async () => {
await fetchUsers()
}
</script>
<style scoped>
.user-manager {
padding: 20px;
}
.users-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.user-item {
padding: 15px;
border: 1px solid #ddd;
border-radius: 8px;
}
.delete-btn {
background-color: #dc3545;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
}
.error {
color: #dc3545;
margin: 10px 0;
}
</style>Error Handling Best Practices
Global Error Handler
Create a global error handler for API errors:
// utils/errorHandler.js
export const handleApiError = (error) => {
if (error.response) {
// Server responded with error status
switch (error.response.status) {
case 400:
return 'Bad request. Please check your input.'
case 401:
return 'Unauthorized. Please log in again.'
case 403:
return 'Forbidden. You don\'t have permission to do this.'
case 404:
return 'Resource not found.'
case 500:
return 'Server error. Please try again later.'
default:
return `Error: ${error.response.status}`
}
} else if (error.request) {
// Request was made but no response received
return 'Network error. Please check your connection.'
} else {
// Something else happened
return error.message || 'An unexpected error occurred.'
}
}Retry Logic
Implement retry logic for failed requests:
// utils/retry.js
export const retryRequest = async (apiCall, maxRetries = 3, delay = 1000) => {
let lastError
for (let i = 0; i < maxRetries; i++) {
try {
return await apiCall()
} catch (error) {
lastError = error
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)))
}
}
}
throw lastError
}Caching and Performance
Simple Cache Implementation
// utils/cache.js
const cache = new Map()
export const cachedApiCall = async (key, apiCall, ttl = 60000) => {
const cached = cache.get(key)
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data
}
const data = await apiCall()
cache.set(key, {
data,
timestamp: Date.now()
})
return data
}Next Steps
Now that you understand API integration, explore Vue.js testing to ensure your API calls work correctly.
External Resources
Last updated on