Se stai sviluppando un'applicazione Node.js che acquisisce documenti caricati dagli utenti, probabilmente hai già fissato una cartella piena di PDF, file DOCX e presentazioni PPTX chiedendoti: come faccio a normalizzare tutto questo in qualcosa che la mia pipeline LLM possa davvero usare?
Questo articolo mostra come usare l'API REST di AnyMD da Node.js — con fetch, axios, tipi TypeScript, streaming e pattern di middleware Express per la conversione da documento a Markdown su scala di produzione.
Perché ottenere Markdown da Node.js?
Le applicazioni Node.js gestiscono naturalmente i caricamenti di file — Multer, Busboy, middleware Express. Ma trasformare quei file in testo pulito e strutturato per pipeline RAG o dati di addestramento IA spesso significa introdurre pesanti binding nativi (PyMuPDF, LibreOffice, pandoc) che non appartengono a un runtime JS.
AnyMD offre un'API HTTP stateless. Una chiamata, una risposta, Markdown pulito. Nessuna dipendenza nativa, nessuna libreria di formati lato server, nessun sottoprocesso Python nella tua applicazione Node.js.
Avvio rapido con fetch (senza dipendenze)
Node.js 18+ include fetch a livello globale. La conversione singola più semplice è:
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);
Tutto qui. AnyMD rileva automaticamente il formato dall'estensione del file e restituisce GitHub-Flavored Markdown.
TypeScript con risposte tipizzate
Per i progetti TypeScript, aggiungi tipi di ritorno appropriati e gestione degli errori:
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 per endpoint di upload
Ecco come collegare AnyMD a un server Express con Multer per i caricamenti di file:
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);
Elaborazione in batch con axios e tentativi automatici
Quando devi elaborare centinaia di documenti, le chiamate sequenziali sono troppo lente. Ecco un processore batch concorrente che usa 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 di documenti di grandi dimensioni
Per documenti oltre 10 MB, usa l'endpoint di streaming per evitare di caricare in memoria l'intero output Markdown.
Confronto con librerie Node.js locali
| Formato | Libreria Node.js | AnyMD |
|---|---|---|
| DOCX | mammoth — nessun supporto PPTX/PDF/XLSX | Una sola API, oltre 15 formati |
pdf-parse — perde tabelle e titoli | GFM, preserva la struttura | |
| PPTX | pptx2md — solo CLI, nessuna API | API REST, pipeline asincrona |
| XLSX | xlsx — esportazione CSV, niente Markdown | Tabelle → tabelle GFM |
| EPUB | epub2md — non mantenuta dal 2022 | API attiva e versionata |
Prestazioni: Node.js comunica con Rust
AnyMD gira su un backend Rust/Axum.
| Formato | Dimensione del documento | AnyMD (ms) | Libreria locale (ms) |
|---|---|---|---|
| DOCX | 50 KB (10 pagine) | 280 | 620 (mammoth) |
| 120 KB (15 pagine) | 410 | 2,100 (pdf-parse) | |
| PPTX | 200 KB (20 diapositive) | 520 | 1,800 (pptx2md) |
| XLSX | 80 KB (5 fogli) | 340 | 950 (xlsx + formattazione manuale) |
Da Markdown a una pipeline RAG
Una volta che la tua applicazione Node.js dispone di Markdown pulito, il passo successivo è fare chunking e creare embedding per la ricerca vettoriale. L'output di AnyMD è l'input ideale per il chunking semantico — vengono preservati i confini dei titoli, la struttura degli elenchi e le intestazioni delle tabelle.
Prezzi
| Piano | Pagine/mese | Dimensione massima del file | Prezzo |
|---|---|---|---|
| Gratuito | 100 | 10 MB | $0 |
| Starter | 500 | 25 MB | $19 |
| Pro | 5,000 | 50 MB | $99 |
| Enterprise | Personalizzato | Personalizzato | Personalizzato |
Un documento tipico viene convertito in meno di 0,5 secondi. 100 pagine gratuite al mese — nessuna carta di credito richiesta.
Ottieni la tua chiave API gratuita e inizia oggi stesso a convertire documenti in Markdown da Node.js.