myanimelist-wrapper
v1.0.2
Published
A comprehensive TypeScript wrapper for the Jikan API v4 (unofficial MyAnimeList API)
Maintainers
Readme
MyAnimeList Wrapper
A type-safe, feature-complete TypeScript wrapper for the Jikan API v4
Documentation • Examples • API Reference • Contributing
Overview
A comprehensive, production-ready TypeScript wrapper for the Jikan API v4 (unofficial MyAnimeList API). Built with type safety, performance, and developer experience in mind.
Provides seamless access to MyAnimeList data including anime, manga, characters, people, and more with full TypeScript support and robust error handling.
✨ Features
- 🎯 Type-Safe: Full TypeScript support with comprehensive type definitions for all API responses
- 📦 Complete Coverage: All Jikan API v4 endpoints implemented and tested
- ⚡ Performance Optimized: Built-in caching, request batching, and connection pooling support
- 🛡️ Robust Error Handling: Custom error classes with detailed error information and recovery suggestions
- 📚 Comprehensive Documentation: Extensive JSDoc comments, examples, and API guides
- 🧪 Well Tested: Full unit test coverage with vitest
- 🔧 Zero Dependencies: Minimal footprint using native Node.js APIs
- 🎨 Clean API: Intuitive, fluent API design for common use cases
- ♻️ Tree-Shakeable: Modular architecture enables optimal bundle sizes
- 📊 Rate Limit Aware: Built-in rate limit handling and request throttling
📦 Installation
Using npm
npm install myanimelist-wrapperUsing yarn
yarn add myanimelist-wrapperUsing pnpm
pnpm add myanimelist-wrapperRequirements
- Node.js 16 or higher
- TypeScript 4.7 or higher (for TypeScript projects)
🚀 Quick Start
Basic Usage
import { JikanClient } from 'myanimelist-wrapper';
// Initialize the client
const jikan = new JikanClient();
// Fetch anime information
const getAnime = async () => {
try {
const response = await jikan.anime.getById(5114); // Fullmetal Alchemist: Brotherhood
console.log(response.data.title);
console.log(response.data.score);
} catch (error) {
console.error('Error fetching anime:', error);
}
};
// Search for anime
const searchAnime = async () => {
try {
const results = await jikan.anime.search({
q: 'attack on titan',
limit: 5,
type: 'tv'
});
results.data.forEach(anime => {
console.log(`${anime.title} (${anime.year})`);
});
} catch (error) {
console.error('Error searching anime:', error);
}
};
getAnime();
searchAnime();📖 Documentation
Table of Contents
- Client Configuration - Configure the JikanClient
- API Endpoints - All available endpoints
- Examples - Real-world usage examples
- Error Handling - Handle errors gracefully
- Rate Limiting - Respect API rate limits
- Pagination - Navigate through results
- Advanced Usage - Advanced patterns and techniques
- TypeScript Types - Type definitions
For complete API reference, see API Documentation.
Client Configuration
import { JikanClient } from 'myanimelist-wrapper';
// Default configuration
const jikan = new JikanClient();
// Custom configuration
const jikanCustom = new JikanClient({
// Base URL of the Jikan API
baseUrl: 'https://api.jikan.moe/v4',
// Request timeout in milliseconds
timeout: 30000,
// Custom headers
headers: {
'Accept': 'application/json',
'User-Agent': 'MyAnimeList-Wrapper/1.0'
},
// Enable/disable automatic retries
retryAttempts: 3,
retryDelay: 1000,
// Cache configuration
cacheEnabled: true,
cacheTTL: 3600000 // 1 hour
});Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| baseUrl | string | https://api.jikan.moe/v4 | The base URL for the Jikan API |
| timeout | number | 30000 | Request timeout in milliseconds |
| headers | object | {} | Custom HTTP headers |
| retryAttempts | number | 3 | Number of retry attempts on failure |
| retryDelay | number | 1000 | Delay between retries in milliseconds |
| cacheEnabled | boolean | true | Enable response caching |
| cacheTTL | number | 3600000 | Cache time-to-live in milliseconds |
🔌 Available Endpoints
The client provides access to all Jikan API v4 endpoints through these properties:
| Endpoint | Module | Methods |
|----------|--------|---------|
| Anime | jikan.anime | getById(), search(), getCharacters(), getEpisodes(), getStatistics(), getForums(), getNews(), getRecommendations(), getRelations() |
| Manga | jikan.manga | getById(), search(), getCharacters(), getForums(), getNews(), getRelations() |
| Characters | jikan.characters | getById(), search(), getPictures() |
| People | jikan.people | getById(), search(), getPictures() |
| Clubs | jikan.clubs | getById(), search(), getRelations(), getMembers() |
| Seasons | jikan.seasons | getCurrent(), getSeason(), getUpcoming() |
| Schedules | jikan.schedules | getSchedule() |
| Top | jikan.top | getAnime(), getManga(), getCharacters(), getPeople() |
| Genres | jikan.genres | getAnimeGenres(), getMangaGenres(), getAnimeExplicitGenres(), getMangaExplicitGenres() |
| Producers | jikan.producers | getById(), search() |
| Magazines | jikan.magazines | getById(), search() |
| Users | jikan.users | getById(), search(), getFullProfile(), getHistory() |
| Reviews | jikan.reviews | getAnimeReviews(), getMangaReviews() |
| Recommendations | jikan.recommendations | getRecommendations() |
| Random | jikan.random | getAnime(), getManga(), getCharacter(), getPerson(), getUser() |
📚 Examples
Complete examples are available in the examples/ directory:
- Basic Usage - Simple API calls
- Advanced Usage - Complex scenarios
- Error Handling - Proper error management
- Pagination - Navigating through paginated results
- Rate Limiting - Respecting API limits
Anime
Get anime information:
// Get anime by ID with full details
const anime = await jikan.anime.getById(5114);
console.log(anime.data.title); // "Fullmetal Alchemist: Brotherhood"
console.log(anime.data.score); // 9.1
console.log(anime.data.episodes); // 64
console.log(anime.data.aired.from); // "2009-04-05"Search anime:
const results = await jikan.anime.search({
q: 'one piece',
type: 'tv',
status: 'airing',
rating: 'pg13',
score: 7,
limit: 25,
page: 1
});
results.data.forEach(anime => {
console.log(`${anime.title} - ${anime.score}`);
});
// Check pagination
if (results.pagination.has_next_page) {
const nextPage = await jikan.anime.search({
q: 'one piece',
page: 2
});
}Get anime characters and voice actors:
const characters = await jikan.anime.getCharacters(5114);
characters.data.forEach(char => {
console.log(`${char.character.name} (${char.role})`);
if (char.voice_actors.length > 0) {
console.log(` VA: ${char.voice_actors[0].person.name}`);
}
});Get anime episodes:
const episodes = await jikan.anime.getEpisodes(5114, 1); // Page 1
episodes.data.forEach(ep => {
console.log(`Episode ${ep.mal_id}: ${ep.title}`);
console.log(` Air date: ${ep.aired}`);
});Get anime statistics:
const stats = await jikan.anime.getStatistics(5114);
stats.data.forEach(stat => {
console.log(`${stat.title}: ${stat.count} users`);
});Manga
Get manga information:
const manga = await jikan.manga.getById(1);
console.log(manga.data.title); // "Berserk"
console.log(manga.data.type); // "Manga"
console.log(manga.data.chapters); // 364 (or null if ongoing)Search manga:
const results = await jikan.manga.search({
q: 'berserk',
type: 'manga',
status: 'publishing',
score: 8,
limit: 10
});
results.data.forEach(manga => {
console.log(`${manga.title} (${manga.chapters} chapters)`);
});Get manga characters:
const characters = await jikan.manga.getCharacters(1);
characters.data.forEach(char => {
console.log(`${char.character.name} (${char.role})`);
});Characters
Get character information:
const character = await jikan.characters.getById(1);
console.log(character.data.name); // "Monkey D. Luffy"
console.log(character.data.about); // Character biography
console.log(character.data.mal_id); // MyAnimeList IDSearch characters:
const results = await jikan.characters.search({
q: 'luffy',
limit: 10,
page: 1
});
results.data.forEach(char => {
console.log(`${char.name} from ${char.anime[0]?.title}`);
});Get character pictures:
const pictures = await jikan.characters.getPictures(1);
pictures.data.forEach(pic => {
console.log(pic.jpg.image_url);
});Seasons
Get current season:
const currentSeason = await jikan.seasons.getCurrent();
currentSeason.data.forEach(anime => {
console.log(`${anime.title} (${anime.season})`);
});Get specific season:
const winter2024 = await jikan.seasons.getSeason(2024, 'winter');
winter2024.data.forEach(anime => {
console.log(`${anime.title} - Episodes: ${anime.episodes}`);
});Get upcoming season:
const upcoming = await jikan.seasons.getUpcoming();
upcoming.data.slice(0, 5).forEach(anime => {
console.log(`${anime.title} (${anime.aired.from})`);
});Top
Get top anime:
const topAnime = await jikan.top.getAnime({
filter: 'airing',
limit: 25,
page: 1
});
topAnime.data.forEach((anime, index) => {
console.log(`${index + 1}. ${anime.title} - ${anime.score}`);
});Get top manga:
const topManga = await jikan.top.getManga({
filter: 'publishing',
limit: 10
});
topManga.data.forEach((manga, index) => {
console.log(`${index + 1}. ${manga.title} - ${manga.score}`);
});Get top characters and people:
const topCharacters = await jikan.top.getCharacters({ limit: 10 });
const topPeople = await jikan.top.getPeople({ limit: 10 });Random
Get random content:
const randomAnime = await jikan.random.getAnime();
console.log(`Random anime: ${randomAnime.data.title}`);
const randomManga = await jikan.random.getManga();
console.log(`Random manga: ${randomManga.data.title}`);
const randomChar = await jikan.random.getCharacter();
console.log(`Random character: ${randomChar.data.name}`);Users
Get user profile:
const user = await jikan.users.getById('firrthecreator');
console.log(user.data.username);
console.log(user.data.gender);
console.log(user.data.joined_date);Get user full profile:
const fullProfile = await jikan.users.getFullProfile('firrthecreator');
console.log(fullProfile.data.statistics);
console.log(fullProfile.data.favorites);Get user history:
const history = await jikan.users.getHistory('firrthecreator', 'anime');
history.data.forEach(entry => {
console.log(`${entry.entry.title} - ${entry.increment} episodes`);
});Other Endpoints
Genres:
const animeGenres = await jikan.genres.getAnimeGenres();
animeGenres.data.forEach(genre => {
console.log(`${genre.name} - ${genre.count} anime`);
});Producers:
const producer = await jikan.producers.getById(1);
console.log(producer.data.titles[0].title);
const producerSearch = await jikan.producers.search({ query: 'sunrise' });Magazines:
const magazines = await jikan.magazines.search({ query: 'jump' });Reviews:
const animeReviews = await jikan.reviews.getAnimeReviews();
animeReviews.data.forEach(review => {
console.log(`${review.anime.title} - ${review.score}/10`);
console.log(review.review.substring(0, 100) + '...');
});Recommendations:
const recommendations = await jikan.recommendations.getRecommendations();
recommendations.data.forEach(rec => {
console.log(
`If you like ${rec.entry_1.title}, try ${rec.entry_2.title}`
);
});🛡️ Error Handling
The client provides a custom JikanError class for comprehensive error handling:
import { JikanError } from 'myanimelist-wrapper';
try {
const anime = await jikan.anime.getById(999999999);
} catch (error) {
if (error instanceof JikanError) {
// API-level error
console.error(`API Error [${error.status}]: ${error.message}`);
if (error.status === 404) {
console.log('Anime not found');
} else if (error.status === 429) {
console.log('Rate limited - retry after delay');
} else if (error.status === 500) {
console.log('Server error - try again later');
}
// Additional error details
if (error.data) {
console.error('Error details:', error.data);
}
} else if (error instanceof TypeError) {
// Network error
console.error('Network error:', error.message);
} else {
// Other errors
console.error('Unexpected error:', error);
}
}Common Error Codes:
| Status | Meaning | Solution |
|--------|---------|----------|
| 400 | Bad Request | Check your query parameters |
| 404 | Not Found | Verify the ID/resource exists |
| 429 | Too Many Requests | Implement rate limiting (see below) |
| 500 | Server Error | Wait and retry |
⏱️ Rate Limiting
The Jikan API enforces rate limits. Respect them using these strategies:
Simple Delay Between Requests:
// Delay helper
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
// Fetch with delays
async function fetchAnimeList(ids: number[]) {
for (const id of ids) {
try {
const anime = await jikan.anime.getById(id);
console.log(anime.data.title);
// Wait 400ms between requests to avoid rate limiting
await wait(400);
} catch (error) {
console.error(`Failed to fetch anime ${id}:`, error);
}
}
}
fetchAnimeList([1, 5, 10, 15, 20]);Queue-Based Rate Limiting:
class RequestQueue {
private queue: (() => Promise<any>)[] = [];
private processing = false;
private delayMs = 400;
async add<T>(fn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
});
this.process();
});
}
private async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const fn = this.queue.shift();
if (fn) {
await fn();
await new Promise(r => setTimeout(r, this.delayMs));
}
}
this.processing = false;
}
}
// Usage
const queue = new RequestQueue();
const animes = [1, 5, 10, 15, 20];
const results = await Promise.all(
animes.map(id => queue.add(() => jikan.anime.getById(id)))
);📄 Pagination
Navigate through paginated results:
interface PaginationOptions {
limit?: number; // Items per page (default: 25, max: 25)
page?: number; // Page number (starts at 1)
}
// Get first page
const firstPage = await jikan.anime.search({
q: 'naruto',
limit: 10,
page: 1
});
console.log(`Results: ${firstPage.data.length}`);
console.log(`Current page: ${firstPage.pagination.current_page}`);
console.log(`Last page: ${firstPage.pagination.last_page}`);
console.log(`Has next: ${firstPage.pagination.has_next_page}`);
// Fetch all pages
async function fetchAllPages<T>(
getPage: (page: number) => Promise<{ data: T[], pagination: any }>
) {
const allResults: T[] = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await getPage(page);
allResults.push(...response.data);
hasMore = response.pagination.has_next_page;
page++;
// Rate limiting
await new Promise(r => setTimeout(r, 400));
}
return allResults;
}
// Usage
const allAnimes = await fetchAllPages(
page => jikan.anime.search({ q: 'dragon', page })
);🔧 Advanced Usage
Parallel Requests with Rate Limiting:
async function batchFetch(ids: number[], batchSize = 5) {
const results = [];
for (let i = 0; i < ids.length; i += batchSize) {
const batch = ids.slice(i, i + batchSize);
// Fetch batch in parallel
const batchResults = await Promise.all(
batch.map(id => jikan.anime.getById(id))
);
results.push(...batchResults);
// Delay between batches
if (i + batchSize < ids.length) {
await new Promise(r => setTimeout(r, 1000));
}
}
return results;
}
const animes = await batchFetch([1, 5, 10, 15, 20, 25], 3);Caching Layer:
class CachedClient {
private cache = new Map<string, { data: any, expiry: number }>();
private cacheTTL = 3600000; // 1 hour
async get<T>(key: string, fn: () => Promise<T>): Promise<T> {
const cached = this.cache.get(key);
if (cached && cached.expiry > Date.now()) {
return cached.data;
}
const data = await fn();
this.cache.set(key, { data, expiry: Date.now() + this.cacheTTL });
return data;
}
clear() {
this.cache.clear();
}
}
// Usage
const client = new CachedClient();
const anime = await client.get('anime:5114', () =>
jikan.anime.getById(5114)
);📝 TypeScript Types
Full type definitions are available for all responses:
import type {
Anime,
Manga,
Character,
Person,
AnimeSearchQuery,
MangaSearchQuery,
ApiResponse,
JikanError
} from 'myanimelist-wrapper';
// Type-safe API calls
async function exampleUsage() {
const response: ApiResponse<Anime> = await jikan.anime.getById(5114);
const anime: Anime = response.data;
// TypeScript knows about all properties
console.log(anime.episodes);
console.log(anime.aired);
console.log(anime.score);
// Search with type-safe parameters
const query: AnimeSearchQuery = {
q: 'naruto',
type: 'tv',
status: 'airing',
score: 8,
limit: 10
};
const searchResults: ApiResponse<Anime[]> =
await jikan.anime.search(query);
}🤝 Contributing
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
Development Setup
# Clone the repository
git clone https://github.com/firrthecreator/myanimelist-wrapper.git
cd myanimelist-wrapper
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Generate coverage report
npm run test:coverage
# Format code
npm run format
# Lint code
npm run lintProject Structure
├── src/
│ ├── client.ts # Main client class
│ ├── index.ts # Package exports
│ ├── endpoints/ # API endpoint implementations
│ └── types/ # TypeScript type definitions
├── tests/ # Test files
├── docs/ # Documentation
├── examples/ # Usage examples
└── package.json # Dependencies and scripts📄 License
MIT © 2026 Firr, The Creator
🙏 Acknowledgments
- Jikan API - The wonderful unofficial MyAnimeList API
- MyAnimeList - The source of all anime/manga data
- Contributors and maintainers of this project
📞 Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Complete API Documentation
Made with ❤️ by Firr, The Creator
