Files
AniKotoAPI/src/routes/apiRoutes.js
T
Shinei Nouzen fe51275db0 feat: upgrade to v2.0.0 with multi-mirror fallback, new endpoints, and LRU cache
- Add multi-mirror fallback system with 5 mirror domains
- Add LRU cache with configurable TTL per endpoint
- Add response compression (gzip)
- Add Seasons endpoint (/api/seasons/:id)
- Add Watch Order endpoint (/api/watch-order/:id)
- Add Latest Updated endpoint (/api/latest-updated)
- Add Download endpoint (/api/download)
- Add cache stats endpoint (/api/cache/stats)
- Add mirror status/reset endpoints (/api/mirrors, /api/mirrors/reset)
- Add configurable rate limiting with X-RateLimit headers
- Add request timeouts and entity too large handling
- Update documentation with all new endpoints
- Update CHANGELOG.md with v2.0.0 entry
- Update package.json version to 2.0.0
- Add 6 AI agent prompts for development
2026-07-04 15:07:07 +05:30

515 lines
22 KiB
JavaScript

/*
* ======= • ======= • ======= • ======= • =======• =======
* AniKotoAPI — apiRoutes.js
* Repository: https://github.com/Shineii86/AniKotoAPI
*
* @description
* Central API router that maps all Express GET endpoints to their
* corresponding controller handlers. Each route wraps its controller
* in try/catch for consistent error handling.
*
* @exports
* createApiRoutes - Function that registers all API routes on the Express app
*
* @author Shinei Nouzen
* @license MIT
* ======= • ======= • ======= • ======= • =======• =======
*/
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const { version } = require("../../package.json");
import { getHomeInfo } from "../controllers/homeInfo.controller.js";
import { getAnimeInfo } from "../controllers/animeInfo.controller.js";
import { getSearchResults, getSearchSuggestions } from "../controllers/search.controller.js";
import { getEpisodeList } from "../controllers/episodeList.controller.js";
import { getEpisodeListAjax } from "../controllers/episodeListAjax.controller.js";
import { getStreamInfo, getServerList, getMapperServers } from "../controllers/streamInfo.controller.js";
import { getSchedule } from "../controllers/schedule.controller.js";
import { getSpotlight } from "../controllers/spotlight.controller.js";
import { getTrending } from "../controllers/trending.controller.js";
import { getTopTen } from "../controllers/topten.controller.js";
import { getSuggestions } from "../controllers/suggestion.controller.js";
import { getRandom } from "../controllers/random.controller.js";
import { getPopular } from "../controllers/popular.controller.js";
import { getFilter } from "../controllers/filter.controller.js";
import { getWatchPage } from "../controllers/watchPage.controller.js";
import { getAzList } from "../controllers/azList.controller.js";
import { getNewRelease, getNewlyAdded, getLatestUpdated } from "../controllers/newRelease.controller.js";
import { getStatus } from "../controllers/status.controller.js";
import { getTrendingSidebar } from "../controllers/trendingSidebar.controller.js";
import { getSeasons } from "../controllers/seasons.controller.js";
import { getWatchOrder } from "../controllers/watchOrder.controller.js";
import { getDownloadLinks } from "../controllers/download.controller.js";
import { categoryRoutes } from "./category.route.js";
import { getMirrorStatus, resetMirrorCache } from "../helper/mirror.helper.js";
// ══════════════════════════════════════════════════════════════
// ROUTE REGISTRATION
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Main API route registration function ----
/**
* Registers all API routes on the Express app instance.
* Each route delegates to a controller and uses the provided
* jsonResponse/jsonError helpers for consistent output.
*
* @param {object} app - Express application instance
* @param {Function} jsonResponse - Helper to wrap data in success JSON
* @param {Function} jsonError - Helper to return error JSON
*
* @example
* createApiRoutes(app, jsonResponse, jsonError);
*/
const createApiRoutes = (app, jsonResponse, jsonError) => {
// ══════════════════════════════════════════════════════════════
// REQUEST COUNTER (must be before all routes)
// ══════════════════════════════════════════════════════════════
const startTime = Date.now();
let requestCount = 0;
let errorCount = 0;
app.use((req, res, next) => {
if (req.path.startsWith("/api")) {
requestCount++;
res.on("finish", () => {
if (res.statusCode >= 400) errorCount++;
});
}
next();
});
// ══════════════════════════════════════════════════════════════
// HOME
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Homepage data endpoint ----
app.get("/api/", async (req, res, next) => {
try {
await getHomeInfo(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// ANIME INFO
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Anime detail information by slug ----
app.get("/api/info", async (req, res, next) => {
try {
await getAnimeInfo(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// WATCH PAGE
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Watch page data for specific episode ----
app.get("/api/watch", async (req, res, next) => {
try {
await getWatchPage(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// SEARCH
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Full search results with pagination ----
app.get("/api/search", async (req, res, next) => {
try {
await getSearchResults(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Search autocomplete suggestions ----
app.get("/api/search/suggest", async (req, res, next) => {
try {
await getSearchSuggestions(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// EPISODES
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Episode list for anime by ID ----
app.get("/api/episodes/:id", async (req, res, next) => {
try {
await getEpisodeList(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: AJAX-loaded episode list (additional data) ----
app.get("/api/episodes-ajax/:id", async (req, res, next) => {
try {
await getEpisodeListAjax(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// STREAMING
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Stream URL and metadata for episode ----
app.get("/api/stream", async (req, res, next) => {
try {
await getStreamInfo(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Available streaming servers for episode ----
app.get("/api/servers", async (req, res, next) => {
try {
await getServerList(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Mapped servers across multiple episodes ----
app.get("/api/mapper-servers", async (req, res, next) => {
try {
await getMapperServers(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Download links for episode ----
app.get("/api/download", async (req, res, next) => {
try {
await getDownloadLinks(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// SCHEDULE
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Anime airing schedule by date ----
app.get("/api/schedule", async (req, res, next) => {
try {
await getSchedule(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// DISCOVERY / HOMEPAGE SECTIONS
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Spotlight/featured anime carousel ----
app.get("/api/spotlight", async (req, res, next) => {
try {
await getSpotlight(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Currently trending anime ----
app.get("/api/trending", async (req, res, next) => {
try {
await getTrending(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Top 10 ranked anime ----
app.get("/api/top-ten", async (req, res, next) => {
try {
await getTopTen(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Auto-complete suggestions by keyword ----
app.get("/api/suggestions", async (req, res, next) => {
try {
await getSuggestions(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Random anime recommendation ----
app.get("/api/random", async (req, res, next) => {
try {
await getRandom(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Most popular anime list ----
app.get("/api/most-popular", async (req, res, next) => {
try {
await getPopular(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Advanced filter/search by parameters ----
app.get("/api/filter", async (req, res, next) => {
try {
await getFilter(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// RELEASE LISTS
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Recently released episodes ----
app.get("/api/new-release", async (req, res, next) => {
try {
await getNewRelease(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Newly added anime series ----
app.get("/api/newly-added", async (req, res, next) => {
try {
await getNewlyAdded(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Recently updated anime (sorted by update time) ----
app.get("/api/latest-updated", async (req, res, next) => {
try {
await getLatestUpdated(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// SIDEBAR
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Trending sidebar widget data ----
app.get("/api/trending-sidebar", async (req, res, next) => {
try {
await getTrendingSidebar(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// SEASONS & WATCH ORDER
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Season information for anime ----
app.get("/api/seasons/:id", async (req, res, next) => {
try {
await getSeasons(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Recommended watch order for anime ----
app.get("/api/watch-order/:id", async (req, res, next) => {
try {
await getWatchOrder(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// A-Z LIST
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Alphabetical anime listing ----
app.get("/api/az-list/:letter", async (req, res, next) => {
try {
await getAzList(req, res, next);
} catch (error) {
jsonError(res, error.message);
}
});
// ══════════════════════════════════════════════════════════════
// CATEGORY ROUTES
// ══════════════════════════════════════════════════════════════
// NOTE: Category routes (genre, type, status) are registered separately
categoryRoutes(app, jsonResponse, jsonError);
// ══════════════════════════════════════════════════════════════
// HEALTH & STATS
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Health check endpoint ----
app.get("/api/health", (req, res) => {
const uptime = Math.floor((Date.now() - startTime) / 1000);
const hours = Math.floor(uptime / 3600);
const minutes = Math.floor((uptime % 3600) / 60);
const seconds = uptime % 60;
res.json({
success: true,
results: {
status: "healthy",
version: version,
uptime: `${hours}h ${minutes}m ${seconds}s`,
uptimeSeconds: uptime,
timestamp: new Date().toISOString(),
node: process.version,
memory: {
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + "MB",
total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024) + "MB",
},
},
});
});
// ---- FEATURE: Cache & API statistics ----
app.get("/api/stats", (req, res) => {
const uptime = Math.floor((Date.now() - startTime) / 1000);
res.json({
success: true,
results: {
uptime: uptime + "s",
requests: {
total: requestCount,
errors: errorCount,
successRate: requestCount > 0
? ((1 - errorCount / requestCount) * 100).toFixed(1) + "%"
: "100%",
},
cache: {
type: "in-memory",
ttl: "5 minutes",
description: "Map-based cache with TTL expiration",
},
endpoints: 30,
timestamp: new Date().toISOString(),
},
});
});
// ---- FEATURE: Mirror status and health check ----
app.get("/api/mirrors", async (req, res) => {
try {
const status = await getMirrorStatus();
res.json({ success: true, results: status });
} catch (error) {
jsonError(res, error.message);
}
});
// ---- FEATURE: Reset mirror cache ----
app.post("/api/mirrors/reset", (req, res) => {
resetMirrorCache();
res.json({ success: true, message: "Mirror cache reset" });
});
// ---- FEATURE: OpenAPI specification ----
app.get("/api/openapi", (req, res) => {
res.json({
openapi: "3.0.3",
info: {
title: "AniKotoAPI",
description: "Free REST API for anime data from anikototv.to",
version: version,
contact: {
name: "Shinei Nouzen",
url: "https://github.com/Shineii86/AniKotoAPI",
},
},
servers: [
{ url: "https://anikototvapi.vercel.app/api", description: "Production" },
],
paths: {
"/": { get: { summary: "Homepage data", tags: ["Home"] } },
"/search": { get: { summary: "Search anime", tags: ["Search"] } },
"/info": { get: { summary: "Anime info", tags: ["Anime"] } },
"/episodes/{id}": { get: { summary: "Episode list", tags: ["Episodes"] } },
"/servers": { get: { summary: "Server list", tags: ["Streaming"] } },
"/stream": { get: { summary: "Stream URL", tags: ["Streaming"] } },
"/download": { get: { summary: "Download links", tags: ["Streaming"] } },
"/schedule": { get: { summary: "Anime schedule", tags: ["Schedule"] } },
"/random": { get: { summary: "Random anime", tags: ["Discovery"] } },
"/top-ten": { get: { summary: "Top 10 rankings", tags: ["Rankings"] } },
"/trending": { get: { summary: "Trending anime", tags: ["Discovery"] } },
"/spotlight": { get: { summary: "Spotlight anime", tags: ["Discovery"] } },
"/most-popular": { get: { summary: "Most popular", tags: ["Rankings"] } },
"/new-release": { get: { summary: "New releases", tags: ["Releases"] } },
"/filter": { get: { summary: "Advanced filter", tags: ["Filter"] } },
"/genre/{name}": { get: { summary: "By genre", tags: ["Categories"] } },
"/type/{name}": { get: { summary: "By type", tags: ["Categories"] } },
"/status/{name}": { get: { summary: "By status", tags: ["Categories"] } },
"/health": { get: { summary: "Health check", tags: ["System"] } },
"/stats": { get: { summary: "API statistics", tags: ["System"] } },
"/mirrors": { get: { summary: "Mirror status", tags: ["System"] } },
"/mirrors/reset": { post: { summary: "Reset mirror cache", tags: ["System"] } },
"/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } },
"/watch": { get: { summary: "Watch page data", tags: ["Streaming"] } },
"/search/suggest": { get: { summary: "Search suggestions", tags: ["Search"] } },
"/episodes-ajax/{id}": { get: { summary: "AJAX episode list", tags: ["Episodes"] } },
"/mapper-servers": { get: { summary: "Mapper servers", tags: ["Streaming"] } },
"/newly-added": { get: { summary: "Newly added", tags: ["Releases"] } },
"/latest-updated": { get: { summary: "Recently updated", tags: ["Releases"] } },
"/trending-sidebar": { get: { summary: "Trending sidebar", tags: ["Discovery"] } },
"/seasons/{id}": { get: { summary: "Season data", tags: ["Anime"] } },
"/watch-order/{id}": { get: { summary: "Watch order", tags: ["Anime"] } },
"/az-list/{letter}": { get: { summary: "A-Z listing", tags: ["Discovery"] } },
"/suggestions": { get: { summary: "Anime suggestions", tags: ["Search"] } },
},
tags: [
{ name: "Home", description: "Homepage data" },
{ name: "Search", description: "Search & suggestions" },
{ name: "Anime", description: "Anime details" },
{ name: "Episodes", description: "Episode lists" },
{ name: "Streaming", description: "Stream URLs & servers" },
{ name: "Schedule", description: "Airing schedule" },
{ name: "Discovery", description: "Random, trending, spotlight" },
{ name: "Rankings", description: "Top 10, most popular" },
{ name: "Releases", description: "New releases" },
{ name: "Filter", description: "Advanced filtering" },
{ name: "Categories", description: "Genre, type, status" },
{ name: "System", description: "Health, stats, OpenAPI" },
],
});
});
};
export { createApiRoutes };
// ══════════════════════════════════════════════════════════════ END: apiRoutes.js