Overview
AniDojo uses the Jikan API to fetch anime data from MyAnimeList. Jikan is a free, unofficial REST API that provides comprehensive anime, manga, and character information from the largest anime database.
Why Jikan API?
Free & No API Key No registration or API key required. Start using immediately.
Comprehensive Data 50,000+ anime with detailed information from MyAnimeList.
Real-Time Updates Live data synced with MyAnimeList database.
Rich Metadata Genres, studios, ratings, episodes, and more.
No Configuration Required
Unlike most APIs, Jikan requires no setup or credentials . The integration is ready to use out of the box!
No environment variables or API keys needed. Just start making requests!
Rate Limiting
Jikan has rate limits to ensure fair usage:
3 requests per second
60 requests per minute
AniDojo’s API client handles this automatically with built-in rate limiting and request queuing.
API Client Implementation
The API client is located in src/lib/animeApi.ts and includes:
Requests are automatically queued and throttled: class APIRateLimiter {
private minInterval = 350 ; // 350ms = ~3 requests/second
private queue : Array < Request > = [];
async call < T >( url : string ) : Promise < T > {
// Queues request and processes with rate limiting
}
}
Responses are cached for 5 minutes to reduce API calls: private cache = new Map < string , { data : any ; timestamp : number }>();
private cacheExpiry = 5 * 60 * 1000 ; // 5 minutes
Failed requests are automatically retried with exponential backoff: private async fetchWithRetry (
url : string ,
retries = 3 ,
backoff = 1000
): Promise < Response > {
// Retry logic with exponential backoff
}
Available API Functions
All API functions are exported from src/lib/animeApi.ts:
Search Anime
Search for anime by title:
import { searchAnime } from '@/lib/animeApi' ;
const results = await searchAnime ( 'Naruto' , 1 , 20 );
// Returns: { data: JikanAnime[], pagination: {...} }
Parameters:
query - Search term
page - Page number (default: 1)
limit - Results per page (default: 20, max: 25)
Get Top Anime
Fetch the highest-rated anime:
import { getTopAnime } from '@/lib/animeApi' ;
const topAnime = await getTopAnime ( 1 , 20 );
// Returns top-rated anime from MyAnimeList
Optional Filters:
// Filter by type
const topTV = await getTopAnime ( 1 , 20 , 'tv' );
const topMovies = await getTopAnime ( 1 , 20 , 'movie' );
Get Anime by ID
Fetch detailed information for a specific anime:
import { getAnimeById } from '@/lib/animeApi' ;
const anime = await getAnimeById ( 1 );
// Returns full anime details
Get Seasonal Anime
Fetch anime from a specific season:
import { getSeasonalAnime } from '@/lib/animeApi' ;
const winterAnime = await getSeasonalAnime ( 2024 , 'winter' );
const springAnime = await getSeasonalAnime ( 2024 , 'spring' );
Seasons: winter, spring, summer, fall
Get Current Season
Fetch anime airing this season:
import { getCurrentSeasonAnime } from '@/lib/animeApi' ;
const currentSeason = await getCurrentSeasonAnime ( 20 );
All responses follow this structure:
interface JikanSearchResponse {
data : JikanAnime [];
pagination : {
last_visible_page : number ;
has_next_page : boolean ;
current_page : number ;
items : {
count : number ;
total : number ;
per_page : number ;
};
};
}
Anime Object Structure
interface JikanAnime {
mal_id : number ; // MyAnimeList ID
title : string ; // Romaji title
title_english ?: string ; // English title
title_japanese ?: string ; // Japanese title
images : { // Cover art images
jpg : {
image_url : string ; // Medium size
small_image_url : string ;
large_image_url : string ;
};
webp : { /* same structure */ };
};
type : string ; // TV, Movie, OVA, etc.
episodes : number ; // Total episodes
status : string ; // Airing status
aired : {
from : string ; // Start date
to : string ; // End date
};
score : number ; // MyAnimeList score (1-10)
rank : number ; // Ranking position
popularity : number ; // Popularity rank
synopsis : string ; // Plot description
genres : Array <{ // Genre tags
mal_id : number ;
name : string ;
}>;
studios : Array <{ // Animation studios
mal_id : number ;
name : string ;
}>;
// ... and more!
}
Convert Jikan data to AniDojo’s internal format:
import { convertJikanToAnime } from '@/lib/animeApi' ;
import type { JikanAnime } from '@/lib/animeApi' ;
const jikanAnime : JikanAnime = { /* ... */ };
const anime = convertJikanToAnime ( jikanAnime );
// Returns:
{
id : 'mal-1' ,
title : 'Cowboy Bebop' ,
synopsis : '...' ,
genres : [ 'Action' , 'Adventure' , 'Sci-Fi' ],
studio : 'Sunrise' ,
year : 1998 ,
episodes : 26 ,
status : 'COMPLETED' ,
coverArt : 'https://cdn.myanimelist.net/...' ,
staff : { /* ... */ }
}
React Hooks for Anime Data
AniDojo provides custom React hooks in src/hooks/useAnime.ts:
useAnimeSearch Hook
import { useAnimeSearch } from '@/hooks/useAnime' ;
function SearchComponent () {
const { data , loading , error , hasNextPage } = useAnimeSearch ( 'Naruto' , 1 );
if ( loading ) return < div > Loading ...</ div > ;
if ( error ) return < div > Error : {error. message } </ div > ;
return (
< div >
{ data . map ( anime => (
< AnimeCard key = {anime. mal_id } anime = { anime } />
))}
</ div >
);
}
useTopAnime Hook
import { useTopAnime } from '@/hooks/useAnime' ;
function TopAnimeComponent () {
const { data , loading , error } = useTopAnime ( 1 , 20 );
return (
< div >
{ data . map ( anime => (
< AnimeCard key = {anime. mal_id } anime = { anime } />
))}
</ div >
);
}
Example: Search Page
Here’s how the search page uses the API:
'use client' ;
import { useState } from 'react' ;
import { useAnimeSearch } from '@/hooks/useAnime' ;
import { AnimeSearch } from '@/components/AnimeSearch' ;
export default function SearchPage () {
const [ query , setQuery ] = useState ( '' );
const [ page , setPage ] = useState ( 1 );
const { data , loading , error , hasNextPage } = useAnimeSearch ( query , page );
return (
< div >
< AnimeSearch
onSearch = { setQuery }
results = { data }
loading = { loading }
error = { error }
/>
{ /* Pagination */ }
< div >
< button
onClick = {() => setPage ( p => Math . max ( 1 , p - 1 ))}
disabled = { page === 1 }
>
Previous
</ button >
< span > Page { page } </ span >
< button
onClick = {() => setPage ( p => p + 1 )}
disabled = {! hasNextPage }
>
Next
</ button >
</ div >
</ div >
);
}
Caching Strategies
Built-in Cache
The API client includes automatic caching:
// First call - fetches from API
const anime1 = await searchAnime ( 'Naruto' );
// Second call within 5 minutes - uses cache
const anime2 = await searchAnime ( 'Naruto' );
Clear Cache
Manually clear the cache when needed:
import { clearAPICache } from '@/lib/animeApi' ;
// Clear all cached responses
clearAPICache ();
React Query (Optional)
For more advanced caching, consider adding React Query:
npm install @tanstack/react-query
import { useQuery } from '@tanstack/react-query' ;
import { searchAnime } from '@/lib/animeApi' ;
function useAnimeSearch ( query : string ) {
return useQuery ({
queryKey: [ 'anime' , 'search' , query ],
queryFn : () => searchAnime ( query ),
staleTime: 5 * 60 * 1000 , // 5 minutes
});
}
Error Handling
The API client handles errors gracefully:
Basic Error Handling
With User Feedback
try {
const anime = await searchAnime ( 'Naruto' );
} catch ( error ) {
if ( error . message . includes ( '429' )) {
// Rate limited - already handled with retries
console . error ( 'API rate limited' );
} else if ( error . message . includes ( 'network' )) {
// Network error
console . error ( 'Network error' );
} else {
// Other error
console . error ( 'API error:' , error );
}
}
Testing the API
Test your API integration:
Test search endpoint
curl "https://api.jikan.moe/v4/anime?q=naruto&limit=5"
Test in your app
Navigate to /search in your app and search for “Naruto”
Check rate limiting
Make multiple rapid requests and verify they’re queued properly
Test caching
Search for the same anime twice and check console for cache hit
Alternative Anime APIs
If you want to use a different data source:
GraphQL API with more detailed data
Pros: GraphQL, detailed character info, user data
Cons: More complex queries, requires different client
Website: anilist.co/graphiql
REST API with social features
Pros: User profiles, follows, social features
Cons: Smaller database than MAL
Website: kitsu.docs.apiary.io
For movies and some anime
Best Practices
API Usage Best Practices:
✅ Respect rate limits (handled automatically)
✅ Cache responses when possible
✅ Handle errors gracefully
✅ Show loading states to users
✅ Implement pagination for large result sets
✅ Don’t hammer the API - be a good citizen
Troubleshooting
API returns 429 (Rate Limited)
The client automatically retries with backoff
If persistent, increase minInterval in animeApi.ts
Consider adding more aggressive caching
Search returns empty results
Verify the anime exists on MyAnimeList
Try different search terms (English vs Japanese)
Check API status: status.jikan.moe
Images not loading
MyAnimeList CDN may block some requests
Consider proxying images through your own CDN
Check CORS headers in browser console
Slow API responses
First request may be slower (cache warming)
Consider prefetching popular anime
Implement skeleton loading states
Next Steps
Complete Setup Review all setup steps to ensure everything is configured
Development Guide Start building features with your configured backend
Resources