Usage Examples
Practical examples demonstrating common use cases and integration patterns.
JavaScript / Fetch API
Fetching and Displaying Songs
JavaScript
// Fetch all songs
async function getAllSongs() {
try {
const response = await fetch('https://www.linernotesjazz.com/api/songs');
const songs = await response.json();
// Display songs
songs.forEach(song => {
console.log(`${song.title} by ${song.composer}`);
});
return songs;
} catch (error) {
console.error('Error fetching songs:', error);
}
}
// Search for specific songs
async function searchSongs(query) {
try {
const response = await fetch(
`https://www.linernotesjazz.com/api/songs?search=${encodeURIComponent(query)}`
);
const songs = await response.json();
return songs;
} catch (error) {
console.error('Error searching songs:', error);
}
}
// Get song details with recordings
async function getSongWithRecordings(songId) {
try {
// Fetch song details
const songResponse = await fetch(`https://www.linernotesjazz.com/api/songs/${songId}`);
const song = await songResponse.json();
// Fetch recordings
const recordingsResponse = await fetch(
`https://www.linernotesjazz.com/api/songs/${songId}/recordings`
);
const recordings = await recordingsResponse.json();
return { song, recordings };
} catch (error) {
console.error('Error fetching song details:', error);
}
}
// Usage
getAllSongs();
searchSongs('blue');
getSongWithRecordings(1);
Python / Requests
Building a Jazz Catalog Script
Python
import requests
import json
BASE_URL = 'https://www.linernotesjazz.com'
class JazzAPI:
def __init__(self):
self.base_url = BASE_URL
def get_songs(self, search=None):
"""Get all songs or search by query"""
url = f"{self.base_url}/api/songs"
params = {'search': search} if search else {}
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
def get_song_detail(self, song_id):
"""Get detailed information about a song"""
url = f"{self.base_url}/api/songs/{song_id}"
response = requests.get(url)
response.raise_for_status()
return response.json()
def get_performer(self, performer_id):
"""Get performer details with discography"""
url = f"{self.base_url}/api/performers/{performer_id}"
response = requests.get(url)
response.raise_for_status()
return response.json()
def get_performer_images(self, performer_id):
"""Get all images for a performer"""
url = f"{self.base_url}/api/performers/{performer_id}/images"
response = requests.get(url)
response.raise_for_status()
return response.json()
# Usage example
api = JazzAPI()
# Search for songs
blue_songs = api.get_songs(search='blue')
print(f"Found {len(blue_songs)} songs with 'blue' in the title")
# Get specific song
song = api.get_song_detail(1)
print(f"Song: {song['title']} by {song['composer']}")
# Get performer info
performer = api.get_performer(1)
print(f"Performer: {performer['performer']['name']}")
print(f"Recordings: {len(performer['recordings'])}")
React Integration
Song Search Component
JavaScript (React)
import React, { useState, useEffect } from 'react';
function SongSearch() {
const [query, setQuery] = useState('');
const [songs, setSongs] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (query.length < 2) {
setSongs([]);
return;
}
const searchSongs = async () => {
setLoading(true);
try {
const response = await fetch(
`https://www.linernotesjazz.com/api/songs?search=${encodeURIComponent(query)}`
);
const data = await response.json();
setSongs(data);
} catch (error) {
console.error('Error searching songs:', error);
} finally {
setLoading(false);
}
};
// Debounce search
const timeoutId = setTimeout(searchSongs, 300);
return () => clearTimeout(timeoutId);
}, [query]);
return (
setQuery(e.target.value)}
placeholder="Search for jazz songs..."
/>
{loading && Searching...
}
{songs.map(song => (
-
{song.title}
by {song.composer}
))}
);
}
export default SongSearch;
Swift / iOS Integration
Jazz API Service
Swift
import Foundation
struct Song: Codable {
let id: Int
let title: String
let composer: String?
let structure: String?
let externalReferences: [String: String]?
enum CodingKeys: String, CodingKey {
case id, title, composer, structure
case externalReferences = "external_references"
}
}
struct Performer: Codable {
let id: Int
let name: String
let biography: String?
let birthDate: String?
let deathDate: String?
enum CodingKeys: String, CodingKey {
case id, name, biography
case birthDate = "birth_date"
case deathDate = "death_date"
}
}
class JazzAPIService {
static let shared = JazzAPIService()
private let baseURL = "https://www.linernotesjazz.com"
func fetchSongs(search: String? = nil) async throws -> [Song] {
var urlString = "\(baseURL)/api/songs"
if let search = search {
urlString += "?search=\(search.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
}
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
let songs = try JSONDecoder().decode([Song].self, from: data)
return songs
}
func fetchSongDetail(id: Int) async throws -> Song {
guard let url = URL(string: "\(baseURL)/api/songs/\(id)") else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
let song = try JSONDecoder().decode(Song.self, from: data)
return song
}
func searchPerformers(query: String) async throws -> [Performer] {
let urlString = "\(baseURL)/api/performers?search=\(query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")"
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
let performers = try JSONDecoder().decode([Performer].self, from: data)
return performers
}
}
// Usage in a SwiftUI View
struct SongListView: View {
@State private var songs: [Song] = []
var body: some View {
List(songs, id: \.id) { song in
VStack(alignment: .leading) {
Text(song.title).font(.headline)
if let composer = song.composer {
Text(composer).font(.subheadline)
}
}
}
.task {
do {
songs = try await JazzAPIService.shared.fetchSongs()
} catch {
print("Error fetching songs: \(error)")
}
}
}
}
Command Line (cURL)
Common cURL Commands
Bash
# Get all songs
curl https://www.linernotesjazz.com/api/songs
# Pretty print JSON response
curl https://www.linernotesjazz.com/api/songs | python -m json.tool
# Search for songs with "blue" in title
curl "https://www.linernotesjazz.com/api/songs?search=blue"
# Get specific song details
curl https://www.linernotesjazz.com/api/songs/1
# Get recordings for a song
curl https://www.linernotesjazz.com/api/songs/1/recordings
# Search for performers
curl "https://www.linernotesjazz.com/api/performers?search=miles"
# Get performer with discography
curl https://www.linernotesjazz.com/api/performers/1
# Get performer images
curl https://www.linernotesjazz.com/api/performers/1/images
# Check API health
curl https://www.linernotesjazz.com/api/health
# Save response to file
curl https://www.linernotesjazz.com/api/songs > songs.json
# Include response headers
curl -i https://www.linernotesjazz.com/api/songs
Common Use Cases
Building a Jazz Playlist Generator
Fetch songs and their canonical recordings to build playlists:
JavaScript
async function buildPlaylist(songIds) {
const playlist = [];
for (const songId of songIds) {
// Get song details
const songResponse = await fetch(`https://www.linernotesjazz.com/api/songs/${songId}`);
const song = await songResponse.json();
// Get recordings
const recordingsResponse = await fetch(
`https://www.linernotesjazz.com/api/songs/${songId}/recordings`
);
const recordings = await recordingsResponse.json();
// Find canonical recording
const canonical = recordings.find(r => r.is_canonical);
if (canonical && canonical.spotify_url) {
playlist.push({
title: song.title,
composer: song.composer,
spotify_url: canonical.spotify_url,
album: canonical.album_title
});
}
}
return playlist;
}
// Usage
const jazzStandards = [1, 5, 12, 23, 45];
const playlist = await buildPlaylist(jazzStandards);
console.log('Playlist:', playlist);
Creating a Performer Biography Page
Fetch comprehensive performer information including bio, images, and discography:
JavaScript
async function getPerformerProfile(performerId) {
try {
// Get performer details and recordings
const performerResponse = await fetch(
`https://www.linernotesjazz.com/api/performers/${performerId}`
);
const performerData = await performerResponse.json();
// Get performer images
const imagesResponse = await fetch(
`https://www.linernotesjazz.com/api/performers/${performerId}/images`
);
const images = await imagesResponse.json();
// Find primary image
const primaryImage = images.find(img => img.is_primary) || images[0];
return {
...performerData,
primaryImage,
allImages: images
};
} catch (error) {
console.error('Error fetching performer profile:', error);
throw error;
}
}
// Usage
const profile = await getPerformerProfile(1);
console.log(`${profile.performer.name}`);
console.log(`Recordings: ${profile.recordings.length}`);
console.log(`Primary Image: ${profile.primaryImage?.url}`);
Building a Song Discovery Interface
Create an autocomplete search with detailed results:
JavaScript
class SongDiscovery {
constructor() {
this.baseUrl = 'https://www.linernotesjazz.com';
this.cache = new Map();
}
async searchSongs(query) {
// Check cache
if (this.cache.has(query)) {
return this.cache.get(query);
}
// Search songs
const response = await fetch(
`${this.baseUrl}/api/songs?search=${encodeURIComponent(query)}`
);
const songs = await response.json();
// Enrich with recording count
const enrichedSongs = await Promise.all(
songs.map(async (song) => {
const recordingsResponse = await fetch(
`${this.baseUrl}/api/songs/${song.id}/recordings`
);
const recordings = await recordingsResponse.json();
return {
...song,
recordingCount: recordings.length,
hasCanonical: recordings.some(r => r.is_canonical)
};
})
);
// Cache results
this.cache.set(query, enrichedSongs);
return enrichedSongs;
}
clearCache() {
this.cache.clear();
}
}
// Usage
const discovery = new SongDiscovery();
const results = await discovery.searchSongs('blue');
results.forEach(song => {
console.log(`${song.title} - ${song.recordingCount} recordings`);
});
Error Handling Best Practices
JavaScript
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
// Check if response is ok (status 200-299)
if (!response.ok) {
if (response.status === 404) {
throw new Error('Resource not found');
} else if (response.status === 500) {
throw new Error('Server error - please try again later');
} else if (response.status === 503) {
throw new Error('Service temporarily unavailable');
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
const data = await response.json();
return { success: true, data };
} catch (error) {
console.error('API request failed:', error);
return {
success: false,
error: error.message
};
}
}
// Usage with retry logic
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const result = await fetchWithErrorHandling(url);
if (result.success) {
return result.data;
}
// Wait before retry (exponential backoff)
if (i < maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
throw new Error(`Failed after ${maxRetries} attempts`);
}