Pokud vyvíjíte aplikaci v Node.js, která přijímá dokumenty nahrané uživateli, pravděpodobně jste už hleděli na složku plnou PDF, souborů DOCX a prezentací PPTX a říkali si: jak to všechno normalizovat do podoby, kterou moje LLM pipeline skutečně dokáže použít?
Tento článek ukazuje práci s REST API AnyMD z Node.js — pomocí fetch, axios, typů TypeScript, streamingu a vzorů Express middleware pro produkční konverzi dokumentů do Markdownu.
Proč Markdown z Node.js?
Aplikace Node.js přirozeně zvládají nahrávání souborů — Multer, Busboy, Express middleware. Převod těchto souborů na čistý strukturovaný text pro RAG pipeline nebo AI trénovací data však obvykle znamená těžké nativní bindingy (PyMuPDF, LibreOffice, pandoc), které do JS runtime nepatří.
AnyMD nabízí bezstavové HTTP API. Jedno volání, jedna odpověď, čistý Markdown. Žádné nativní závislosti, žádné serverové knihovny formátů, žádné Python subprocessy v aplikaci Node.js.
Rychlý start s fetch (bez závislostí)
Node.js 18+ poskytuje fetch globálně. Nejjednodušší jednorázová konverze:
const fs = require("fs");
const { readFile } = require("fs/promises");
async function convertToMarkdown(filePath, apiKey) {
const form = new FormData();
form.set("file", new Blob([await readFile(filePath)]), filePath);
const resp = await fetch("https://anymd.net/api/convert", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: form,
});
if (!resp.ok) {
throw new Error(`AnyMD error ${resp.status}: ${await resp.text()}`);
}
return await resp.text();
}
// Usage
const md = await convertToMarkdown("report.docx", "your-api-key");
console.log(md);
A je to. AnyMD automaticky rozpozná formát podle přípony souboru a vrátí GitHub-Flavored Markdown.
TypeScript s typovanými odpověďmi
V projektech TypeScript přidejte správné návratové typy a ošetření chyb:
interface ConversionResult {
markdown: string;
format: string;
pages: number;
duration: number;
}
interface ConversionError {
status: number;
message: string;
}
async function convertToMarkdownTyped(
filePath: string,
apiKey: string
): Promise<ConversionResult> {
const form = new FormData();
const buffer = await fs.promises.readFile(filePath);
form.set("file", new Blob([buffer]), filePath);
const start = performance.now();
const resp = await fetch("https://anymd.net/api/convert", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: form,
});
const duration = performance.now() - start;
if (!resp.ok) {
const err: ConversionError = {
status: resp.status,
message: await resp.text(),
};
throw err;
}
return {
markdown: await resp.text(),
format: filePath.split(".").pop() || "unknown",
pages: parseInt(resp.headers.get("X-Pages") || "0", 10),
duration: Math.round(duration),
};
}
Express middleware pro upload endpointy
Takto zapojíte AnyMD do Express serveru s Multerem pro nahrávání souborů:
import express from "express";
import multer from "multer";
import fs from "fs/promises";
const app = express();
const upload = multer({ dest: "/tmp/uploads/" });
const ANYMD_API = "https://anymd.net/api/convert";
app.post("/upload", upload.single("file"), async (req, res) => {
try {
const apiKey = req.headers["authorization"]?.replace("Bearer ", "");
if (!apiKey) return res.status(401).json({ error: "Missing API key" });
const form = new FormData();
const buffer = await fs.readFile(req.file.path);
form.set("file", new Blob([buffer]), req.file.originalname);
const anymdResp = await fetch(ANYMD_API, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: form,
});
if (!anymdResp.ok) {
return res.status(anymdResp.status).json({
error: await anymdResp.text(),
});
}
const markdown = await anymdResp.text();
res.json({ markdown, pages: anymdResp.headers.get("X-Pages") });
} catch (err) {
res.status(500).json({ error: err.message });
} finally {
if (req.file) fs.unlink(req.file.path).catch(() => {});
}
});
app.listen(3000);
Dávkové zpracování s axios a opakováním
Když potřebujete zpracovat stovky dokumentů, sekvenční volání jsou příliš pomalá. Zde je souběžný dávkový procesor s axios:
import axios from "axios";
import FormData from "form-data";
import fs from "fs";
import path from "path";
import pLimit from "p-limit";
const API = "https://anymd.net/api/convert";
async function convertOne(filePath, apiKey) {
const form = new FormData();
form.append("file", fs.createReadStream(filePath), path.basename(filePath));
for (let attempt = 0; attempt < 3; attempt++) {
try {
const resp = await axios.post(API, form, {
headers: {
...form.getHeaders(),
Authorization: `Bearer ${apiKey}`,
},
maxContentLength: 50 * 1024 * 1024,
timeout: 30_000,
});
const outPath = filePath.replace(path.extname(filePath), ".md");
fs.writeFileSync(outPath, resp.data);
return { file: path.basename(filePath), ok: true };
} catch (err) {
if (attempt === 2) return { file: path.basename(filePath), ok: false, error: err.message };
const delay = Math.pow(1.5, attempt) * 1000;
await new Promise(r => setTimeout(r, delay));
}
}
}
async function batchConvert(dir, apiKey, concurrency = 10) {
const files = fs.readdirSync(dir).filter(f =>
/\.(docx|pdf|pptx|xlsx|epub|odt|rtf)$/i.test(f)
);
const limit = pLimit(concurrency);
const results = await Promise.all(
files.map(f => limit(() => convertOne(path.join(dir, f), apiKey)))
);
const ok = results.filter(r => r.ok).length;
console.log(`${ok}/${results.length} converted`);
return results;
}
await batchConvert("./documents", "your-api-key", 10);
Streamování velkých dokumentů
Pro dokumenty nad 10 MB použijte streamingový endpoint, abyste nemuseli držet celý výstup Markdown v paměti.
Porovnání s lokálními knihovnami Node.js
| Formát | Knihovna Node.js | AnyMD |
|---|---|---|
| DOCX | mammoth — bez podpory PPTX/PDF/XLSX | Jedno API, více než 15 formátů |
pdf-parse — ztrácí tabulky a nadpisy | GFM, zachovává strukturu | |
| PPTX | pptx2md — pouze CLI, bez API | REST API, asynchronní pipeline |
| XLSX | xlsx — export CSV, bez Markdownu | Tabulky → GFM tabulky |
| EPUB | epub2md — neudržované od roku 2022 | Aktivní, verzované API |
Výkon: Node.js komunikuje s Rustem
AnyMD běží na backendu Rust/Axum.
| Formát | Velikost dokumentu | AnyMD (ms) | Lokální knihovna (ms) |
|---|---|---|---|
| DOCX | 50 KB (10 stran) | 280 | 620 (mammoth) |
| 120 KB (15 stran) | 410 | 2,100 (pdf-parse) | |
| PPTX | 200 KB (20 snímků) | 520 | 1,800 (pptx2md) |
| XLSX | 80 KB (5 listů) | 340 | 950 (xlsx + ruční formátování) |
Od Markdownu k RAG pipeline
Jakmile má vaše Node.js aplikace čistý Markdown, dalším krokem je chunkování a embedding pro vektorové vyhledávání. Výstup AnyMD je ideálním vstupem pro sémantické chunkování — hranice nadpisů, struktura seznamů a záhlaví tabulek zůstávají zachovány.
Ceny
| Tarif | Stránek/měsíc | Maximální velikost souboru | Cena |
|---|---|---|---|
| Zdarma | 100 | 10 MB | $0 |
| Starter | 500 | 25 MB | $19 |
| Pro | 5,000 | 50 MB | $99 |
| Enterprise | Na míru | Na míru | Na míru |
Typický dokument se převede za méně než 0,5 sekundy. 100 stránek zdarma měsíčně — bez platební karty.
Získejte bezplatný API klíč a začněte ještě dnes převádět dokumenty do Markdownu z Node.js.