Blog

Conversie documente în Markdown cu Node.js

14 august 2026 · 6 min de citit


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

FormatBibliotecă Node.jsAnyMD
DOCXmammoth — fără suport PPTX/PDF/XLSXUn API, peste 15 formate
PDFpdf-parse — pierde tabele și titluriGFM, păstrează structura
PPTXpptx2md — doar CLI, fără APIAPI REST, pipeline asincron
XLSXxlsx — export CSV, fără MarkdownTabele → tabele GFM
EPUBepub2md — neîntreținut din 2022API activ, versionat

Performanță: Node.js comunică cu Rust

AnyMD rulează pe un backend Rust/Axum.

FormatDimensiune documentAnyMD (ms)Bibliotecă locală (ms)
DOCX50 KB (10 pagini)280620 (mammoth)
PDF120 KB (15 pagini)4102,100 (pdf-parse)
PPTX200 KB (20 slide-uri)5201,800 (pptx2md)
XLSX80 KB (5 foi)340950 (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

PlanPagini/lunăDimensiune maximă fișierPreț
Gratuit10010 MB$0
Starter50025 MB$19
Pro5,00050 MB$99
EnterprisePersonalizatPersonalizatPersonalizat

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.


← Citește mai mult →