API Integration Techniques
Connecting Svelte to Laravel API
Integrating your Svelte frontend with a Laravel backend API is a common pattern for building modern web applications. This guide covers the best practices for API integration.
Setting Up Axios
Axios is a popular HTTP client that simplifies API requests.
// Install axios npm install axios // Create src/lib/api.js import axios from 'axios'; // Create an axios instance with default config const api = axios.create({ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000/api', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, withCredentials: true // For cookie-based auth }); // Request interceptor - add auth token api.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // Response interceptor - handle common errors api.interceptors.response.use( response => response, error => { if (error.response?.status === 401) { // Handle unauthorized (redirect to login, etc.) localStorage.removeItem('token'); window.location.href = '/login'; } return Promise.reject(error); } ); export default api;
API Request Methods
Creating API Service Modules
It's a good practice to organize API calls into service modules by feature or entity.
// src/lib/services/posts.js import api from '../api'; export const PostsService = { // Get all posts getAll: () => api.get('/posts'), // Get a single post by ID getById: (id) => api.get(`/posts/${id}`), // Create a new post create: (data) => api.post('/posts', data), // Update a post update: (id, data) => api.put(`/posts/${id}`, data), // Delete a post delete: (id) => api.delete(`/posts/${id}`) };
Using API Services in Components
<script> import { onMount } from 'svelte'; import { PostsService } from '$lib/services/posts'; let posts = []; let loading = true; let error = null; async function loadPosts() { try { loading = true; const response = await PostsService.getAll(); posts = response.data; } catch (err) { error = err.response?.data?.message || 'Failed to load posts'; } finally { loading = false; } } onMount(loadPosts); </script> {#if loading} <p>Loading...</p> {:else if error} <p class="error">{error}</p> {:else} <ul> {#each posts as post} <li>{post.title}</li> {/each} </ul> {/if}
Using SvelteKit's Data Loading
Load Function for API Data
SvelteKit provides a powerful data loading system with the load
function.
// src/routes/posts/+page.js import { PostsService } from '$lib/services/posts'; export async function load({ fetch }) { try { // Use the fetch instance provided by SvelteKit const response = await PostsService.getAll(); return { posts: response.data }; } catch (error) { return { posts: [], error: error.response?.data?.message || 'Failed to load posts' }; } }
// src/routes/posts/+page.svelte <script> // Access the loaded data export let data; // Destructure the data const { posts, error } = data; </script> {#if error} <p class="error">{error}</p> {:else} <ul> {#each posts as post} <li>{post.title}</li> {/each} </ul> {/if}
API Data Management with Svelte Stores
Creating Reactive Stores for API Data
Using Svelte stores to manage API data provides a reactive and centralized approach.
// src/stores/posts.js import { writable } from 'svelte/store'; import { PostsService } from '$lib/services/posts'; // Create initial state const initialState = { posts: [], loading: false, error: null }; // Create and export the store function createPostsStore() { const { subscribe, set, update } = writable(initialState); return { subscribe, // Load all posts fetchPosts: async () => { update(state => ({ ...state, loading: true, error: null })); try { const response = await PostsService.getAll(); update(state => ({ ...state, posts: response.data, loading: false })); } catch (error) { update(state => ({ ...state, error: error.response?.data?.message || 'Failed to load posts', loading: false })); } }, // Add a new post addPost: async (postData) => { update(state => ({ ...state, loading: true, error: null })); try { const response = await PostsService.create(postData); update(state => ({ ...state, posts: [...state.posts, response.data], loading: false })); return response.data; } catch (error) { update(state => ({ ...state, error: error.response?.data?.message || 'Failed to create post', loading: false })); throw error; } }, // Update an existing post updatePost: async (id, postData) => { update(state => ({ ...state, loading: true, error: null })); try { const response = await PostsService.update(id, postData); update(state => ({ ...state, posts: state.posts.map(post => post.id === id ? response.data : post ), loading: false })); return response.data; } catch (error) { update(state => ({ ...state, error: error.response?.data?.message || 'Failed to update post', loading: false })); throw error; } }, // Delete a post deletePost: async (id) => { update(state => ({ ...state, loading: true, error: null })); try { await PostsService.delete(id); update(state => ({ ...state, posts: state.posts.filter(post => post.id !== id), loading: false })); } catch (error) { update(state => ({ ...state, error: error.response?.data?.message || 'Failed to delete post', loading: false })); throw error; } }, // Reset store to initial state reset: () => set(initialState) }; } export const postsStore = createPostsStore();
Using the Store in Components
<script> import { onMount } from 'svelte'; import { postsStore } from '../stores/posts'; onMount(() => { // Load posts when component mounts postsStore.fetchPosts(); }); // Create a new post async function handleCreatePost(formData) { try { await postsStore.addPost({ title: formData.title, body: formData.body }); // Handle success (e.g., show notification) } catch (error) { // Handle error } } </script> {#if $postsStore.loading} <p>Loading...</p> {:else if $postsStore.error} <p class="error">{$postsStore.error}</p> {:else} <ul> {#each $postsStore.posts as post} <li> {post.title} <button on:click={() => postsStore.deletePost(post.id)}>Delete</button> </li> {/each} </ul> {/if}
Error Handling Strategies
Handling API Errors
Properly handling API errors improves user experience and helps with debugging.
- Global Error Handling - Handle common errors like 401, 403, 500 in Axios interceptors
- Component-Level Error Handling - Use try/catch blocks for specific operations
- Error UI Components - Create reusable error message components
- Form Validation Errors - Handle Laravel validation errors (422 responses)
// Handling Laravel validation errors async function submitForm(formData) { try { const response = await api.post('/endpoint', formData); // Handle success } catch (error) { if (error.response?.status === 422) { // Laravel validation errors const validationErrors = error.response.data.errors; // Map errors to form fields Object.keys(validationErrors).forEach(field => { formErrors[field] = validationErrors[field][0]; }); } else { // Other errors errorMessage = error.response?.data?.message || 'An error occurred'; } } }
Demo: API Integration Example
Posts from API
No posts found.
Best Practices
API Integration Tips
- Use Environment Variables - Store API URLs in
.env
files - Centralize API Logic - Create service modules by feature
- Handle Loading States - Always show loading indicators during requests
- Error Handling - Implement proper error handling at multiple levels
- Type Definitions - Use TypeScript interfaces for API responses
- Caching - Consider caching strategies for frequently accessed data
- Request Cancellation - Use AbortController to cancel pending requests
- Offline Support - Consider implementing offline capabilities