Blog

Konwersja dokumentów na Markdown z Node.js

14 sierpnia 2026 · 6 min czytania


Jeśli tworzysz aplikację Node.js, która przyjmuje dokumenty przesyłane przez użytkowników, prawdopodobnie patrzyłeś już na folder pełen PDF-ów, plików DOCX i prezentacji PPTX, zastanawiając się: jak znormalizować to wszystko do postaci, której mój potok LLM rzeczywiście może użyć?

Ten artykuł pokazuje użycie REST API AnyMD z Node.js — z fetch, axios, typami TypeScript, streamingiem i wzorcami middleware Express do produkcyjnej konwersji dokumentów do Markdown.

Dlaczego Markdown z Node.js?

Aplikacje Node.js naturalnie obsługują przesyłanie plików — Multer, Busboy, middleware Express. Jednak zamiana tych plików w czysty, ustrukturyzowany tekst dla potoków RAG lub danych treningowych AI zwykle wymaga ciężkich natywnych bindingów (PyMuPDF, LibreOffice, pandoc), które nie pasują do środowiska JS.

AnyMD daje bezstanowe HTTP API. Jedno wywołanie, jedna odpowiedź, czysty Markdown. Bez natywnych zależności, bez serwerowych bibliotek formatów, bez podprocesów Pythona w aplikacji Node.js.

Szybki start z fetch (bez zależności)

Node.js 18+ udostępnia fetch globalnie. Najprostsza jednorazowa konwersja:

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);

To wszystko. AnyMD automatycznie rozpoznaje format po rozszerzeniu pliku i zwraca GitHub-Flavored Markdown.

TypeScript z typowanymi odpowiedziami

W projektach TypeScript dodaj odpowiednie typy zwracane i obsługę błędów:

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 dla endpointów uploadu

Tak podłączysz AnyMD do serwera Express z Multerem do obsługi przesyłania plików:

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);

Przetwarzanie wsadowe z axios i ponowieniami

Gdy masz setki dokumentów do przetworzenia, wywołania sekwencyjne są zbyt wolne. Oto współbieżny procesor wsadowy z 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 dużych dokumentów

Dla dokumentów większych niż 10 MB użyj endpointu streamingowego, aby nie buforować całego wyniku Markdown w pamięci.

Porównanie z lokalnymi bibliotekami Node.js

FormatBiblioteka Node.jsAnyMD
DOCXmammoth — brak obsługi PPTX/PDF/XLSXJedno API, ponad 15 formatów
PDFpdf-parse — gubi tabele i nagłówkiGFM, zachowuje strukturę
PPTXpptx2md — tylko CLI, bez APIREST API, potok asynchroniczny
XLSXxlsx — eksport CSV, bez MarkdownTabele → tabele GFM
EPUBepub2md — nieutrzymywana od 2022 rokuAktywne, wersjonowane API

Wydajność: Node.js rozmawia z Rust

AnyMD działa na backendzie Rust/Axum.

FormatRozmiar dokumentuAnyMD (ms)Biblioteka lokalna (ms)
DOCX50 KB (10 stron)280620 (mammoth)
PDF120 KB (15 stron)4102,100 (pdf-parse)
PPTX200 KB (20 slajdów)5201,800 (pptx2md)
XLSX80 KB (5 arkuszy)340950 (xlsx + ręczne formatowanie)

Od Markdown do potoku RAG

Gdy aplikacja Node.js ma już czysty Markdown, następnym krokiem jest chunking i embedding dla wyszukiwania wektorowego. Wynik AnyMD jest idealnym wejściem dla chunkingu semantycznego — granice nagłówków, struktura list i nagłówki tabel są zachowywane.

Cennik

PlanStrony/miesiącMaksymalny rozmiar plikuCena
Bezpłatny10010 MB$0
Starter50025 MB$19
Pro5,00050 MB$99
EnterpriseNiestandardowyNiestandardowyNiestandardowy

Typowy dokument konwertuje się w mniej niż 0,5 sekundy. 100 bezpłatnych stron miesięcznie — bez karty kredytowej.

Pobierz bezpłatny klucz API i zacznij już dziś konwertować dokumenty do Markdown z Node.js.


← Czytaj dalej →