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

Next Steps

Now that you understand API integration techniques, check out: