Dacă construiești o aplicație Node.js care preia documente încărcate de utilizatori, probabil ai privit deja un folder plin cu PDF-uri, fișiere DOCX și prezentări PPTX și te-ai întrebat: cum normalizez toate acestea într-un format pe care pipeline-ul meu LLM îl poate folosi efectiv?
Acest articol prezintă utilizarea API-ului REST AnyMD din Node.js — cu fetch, axios, tipuri TypeScript, streaming și modele de middleware Express pentru conversia documentelor în Markdown la scară de producție.
De ce Markdown din Node.js?
Aplicațiile Node.js gestionează în mod natural încărcările de fișiere — Multer, Busboy, middleware Express. Dar transformarea acestor fișiere în text curat și structurat pentru pipeline-uri RAG sau date de antrenare AI înseamnă de obicei dependințe native grele (PyMuPDF, LibreOffice, pandoc), care nu își au locul într-un runtime JS.
AnyMD îți oferă un API HTTP fără stare. Un apel, un răspuns, Markdown curat. Fără dependințe native, fără biblioteci de formate pe server, fără subprocese Python în aplicația ta Node.js.
Pornire rapidă cu fetch (fără dependințe)
Node.js 18+ include fetch global. Cea mai simplă conversie dintr-un singur pas este:
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);
Asta e tot. AnyMD detectează automat formatul din extensia fișierului și returnează GitHub-Flavored Markdown.
TypeScript cu răspunsuri tipizate
Pentru proiectele TypeScript, adaugă tipuri de returnare potrivite și gestionarea erorilor:
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),
};
}
Middleware Express pentru endpointuri de upload
Iată cum conectezi AnyMD la un server Express cu Multer pentru încărcări de fișiere:
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);
Procesare în lot cu axios și reîncercări
Când ai sute de documente de procesat, apelurile secvențiale sunt prea lente. Iată un procesor concurent în lot folosind 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);
Streaming pentru documente mari
Pentru documente mai mari de 10 MB, folosește endpointul de streaming pentru a evita încărcarea întregului output Markdown în memorie.
Comparație cu biblioteci Node.js locale
| Format | Bibliotecă Node.js | AnyMD |
|---|---|---|
| DOCX | mammoth — fără suport PPTX/PDF/XLSX | Un API, peste 15 formate |
pdf-parse — pierde tabele și titluri | GFM, păstrează structura | |
| PPTX | pptx2md — doar CLI, fără API | API REST, pipeline asincron |
| XLSX | xlsx — export CSV, fără Markdown | Tabele → tabele GFM |
| EPUB | epub2md — neîntreținut din 2022 | API activ, versionat |
Performanță: Node.js comunică cu Rust
AnyMD rulează pe un backend Rust/Axum.
| Format | Dimensiune document | AnyMD (ms) | Bibliotecă locală (ms) |
|---|---|---|---|
| DOCX | 50 KB (10 pagini) | 280 | 620 (mammoth) |
| 120 KB (15 pagini) | 410 | 2,100 (pdf-parse) | |
| PPTX | 200 KB (20 slide-uri) | 520 | 1,800 (pptx2md) |
| XLSX | 80 KB (5 foi) | 340 | 950 (xlsx + formatare manuală) |
De la Markdown la un pipeline RAG
După ce aplicația Node.js are Markdown curat, următorul pas este chunking-ul și embedding-ul pentru căutare vectorială. Outputul AnyMD este intrarea ideală pentru chunking semantic — limitele titlurilor, structura listelor și anteturile tabelelor sunt păstrate.
Prețuri
| Plan | Pagini/lună | Dimensiune maximă fișier | Preț |
|---|---|---|---|
| Gratuit | 100 | 10 MB | $0 |
| Starter | 500 | 25 MB | $19 |
| Pro | 5,000 | 50 MB | $99 |
| Enterprise | Personalizat | Personalizat | Personalizat |
Un document obișnuit se convertește în mai puțin de 0,5 secunde. 100 de pagini gratuite pe lună — fără card de credit.
Obține cheia API gratuită și începe chiar azi să convertești documente în Markdown din Node.js.