Appels API
Dans ce chapitre, on connecte le Show Watchlist à une API REST publique : les films sont récupérés depuis TVMaze au lieu d'être déclarés en dur dans le composant.
Pourquoi appeler une API ?
Jusqu'ici, les données étaient déclarées directement dans le composant. En réalité, elles viennent d'un serveur distant via des appels HTTP.
Vue ne fournit pas de client HTTP intégré. Ce cours utilise fetch natif, disponible partout sans dépendance supplémentaire.
TVMaze API
TVMaze est une API publique de séries TV, gratuite et sans clé API. On l'utilise comme source de données pour notre watchlist.
| Endpoint | Description |
|---|---|
GET /shows | Liste des séries (250 par page) |
GET /shows/:id | Détail d'une série |
GET /search/shows?q=:query | Recherche de séries |
Rappel : fetch natif
// GET
const response = await fetch('https://api.tvmaze.com/shows')
const shows = await response.json()
// Avec paramètre de recherche
const response = await fetch('https://api.tvmaze.com/search/shows?q=batman')
const results = await response.json() // [{ score, show }, ...]
fetchne rejette pas la promesse en cas d'erreur HTTP (404, 500…). Il faut vérifierresponse.okmanuellement.
Type Show
On adapte le type à la structure retournée par TVMaze. Le champ seen est local (TVMaze ne le gère pas) :
// src/types/index.ts
export interface Show {
id: number
name: string
genres: string[]
summary: string | null
image: { medium: string } | null
rating: { average: number | null }
seen: boolean
}
Appels API dans un composant
onMounted est le hook idéal pour déclencher un appel API au chargement d'une page. On gère localement les états isLoading et error.
<!-- src/pages/HomePage.vue -->
<template>
<div>
<p v-if="isLoading">Chargement...</p>
<p v-else-if="error">{{ error }}</p>
<ShowList v-else :shows="shows" @toggle-seen="onToggleSeen">
<NSpace justify="space-between" align="center">
<NInput v-model:value="search" placeholder="Rechercher une série..."/>
<NTag type="success">{{ seenShows.length }} vu(s) sur {{ shows.length }}</NTag>
</NSpace>
</ShowList>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import type { Show } from '@/types'
import ShowList from '../components/ShowList.component.vue'
const BASE_URL = 'https://api.tvmaze.com'
const search = ref<string>('')
const shows = ref<Show[]>([])
const isLoading = ref<boolean>(false)
const error = ref<string | null>(null)
const seenShows = computed(() =>
shows.value.filter((m) => m.seen)
)
const fetchShows = async () => {
isLoading.value = true
try {
const res = await fetch(`${BASE_URL}/shows`)
if (!res.ok) throw new Error(`Erreur ${res.status}`)
const data: Omit<Show, 'seen'>[] = await res.json()
shows.value = data.slice(0, 12).map((m) => ({ ...m, seen: false }))
}
catch (e) {
error.value = e instanceof Error ? e.message : 'Erreur inconnue'
}
finally {
isLoading.value = false
}
}
watch(search, async (newVal) => {
if (!newVal.trim()) {
await fetchShows()
return
}
isLoading.value = true
try {
const res = await fetch(`${BASE_URL}/search/shows?q=${encodeURIComponent(newVal)}`)
if (!res.ok) throw new Error(`Erreur ${res.status}`)
const results: { show: Omit<Show, 'seen'> }[] = await res.json()
shows.value = results.map((r) => ({ ...r.show, seen: false }))
}
catch (e) {
error.value = e instanceof Error ? e.message : 'Erreur inconnue'
}
finally {
isLoading.value = false
}
})
onMounted(fetchShows)
const onToggleSeen = (show: Show) => {
show.seen = !show.seen
}
</script>
Quelques points importants :
data.slice(0, 12)limite l'affichage aux 12 premiers résultats.map((m) => ({ ...m, seen: false }))ajoute le champ localseen- La recherche déclenche un nouvel appel à
/search/shows?q=... toggleSeenest purement local — TVMaze est une API en lecture seule