npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

myanimelist-wrapper

v1.0.2

Published

A comprehensive TypeScript wrapper for the Jikan API v4 (unofficial MyAnimeList API)

Readme

MyAnimeList Wrapper

npm version npm downloads License: MIT CI TypeScript Node.js

A type-safe, feature-complete TypeScript wrapper for the Jikan API v4

DocumentationExamplesAPI ReferenceContributing


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-wrapper

Using yarn

yarn add myanimelist-wrapper

Using pnpm

pnpm add myanimelist-wrapper

Requirements

  • 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

  1. Client Configuration - Configure the JikanClient
  2. API Endpoints - All available endpoints
  3. Examples - Real-world usage examples
  4. Error Handling - Handle errors gracefully
  5. Rate Limiting - Respect API rate limits
  6. Pagination - Navigate through results
  7. Advanced Usage - Advanced patterns and techniques
  8. 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:

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 ID

Search 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 lint

Project 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


Made with ❤️ by Firr, The Creator

⬆ back to top