Favorites in Tourify are polymorphic — a single favorites table stores bookmarks for any content type using the favorable_type and favorable_id columns. The same API endpoints handle places, events, and cities uniformly.
All favorites endpoints require authentication. Include the Authorization: Bearer {your-token} header on every request.
Supported favorable_type values:
Value
Target model
place
A tourist place
event
A scheduled event
city
A city
The database prevents duplicate favorites through firstOrCreate on the (user_id, favorable_id, favorable_type) combination.
Add an item to the authenticated user’s favorites. If the item is already favorited, the existing record is returned (no duplicate is created).Required fields
Remove a favorite by its morph pair (favorable_id + favorable_type) instead of by its own ID. This is the preferred method in the mobile app because the frontend tracks favorites by type and item ID rather than storing the favorite.id.Required fields
The useFavorites hook manages the favorites list in the React Native app. It provides isFavorite(type, id) to check whether an item is bookmarked and toggleFavorite(type, id) to add or remove it atomically. The morph-based delete (DELETE /api/favorites with a body) is used to avoid storing the favorite.id in client state.
hooks/useFavorites.js
import { useCallback, useEffect, useState } from "react";import { del, get, post } from "../services/post";export function useFavorites() { const [favorites, setFavorites] = useState([]); const [loading, setLoading] = useState(true); const load = useCallback(async () => { setLoading(true); try { setFavorites(await get("/favorites")); } catch { setFavorites([]); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); // Check if an item is already favorited const isFavorite = useCallback( (type, id) => favorites.some((f) => f.favorable_type === type && f.favorable_id === id), [favorites], ); // Add or remove a favorite based on current state const toggleFavorite = useCallback( async (type, id) => { if (isFavorite(type, id)) { // Uses morph-based DELETE — no need to know the favorite's own ID await del("/favorites", { favorable_type: type, favorable_id: id }); setFavorites((prev) => prev.filter( (f) => !(f.favorable_type === type && f.favorable_id === id), ), ); } else { const newFav = await post("/favorites", { favorable_type: type, favorable_id: id, }); setFavorites((prev) => [ ...prev, { ...newFav, favorable_type: type, favorable_id: id }, ]); } }, [isFavorite], ); return { favorites, loading, refresh: load, isFavorite, toggleFavorite };}