Blog

Conversion de documents en Markdown avec Node.js

14 août 2026 · 6 min de lecture


Si vous développez une application Node.js qui ingère des documents téléversés par les utilisateurs, vous avez probablement déjà regardé un dossier rempli de PDF, de fichiers DOCX et de présentations PPTX en vous demandant : comment normaliser tout cela pour obtenir quelque chose que mon pipeline LLM puisse réellement utiliser ?

Cet article explique comment utiliser l'API REST d'AnyMD depuis Node.js — avec fetch, axios, des types TypeScript, le streaming et des modèles de middleware Express pour convertir des documents en Markdown à l'échelle de la production.

Pourquoi produire du Markdown depuis Node.js ?

Les applications Node.js gèrent naturellement les téléversements de fichiers — Multer, Busboy, middleware Express. Mais transformer ces fichiers en texte propre et structuré pour des pipelines RAG ou des données d'entraînement d'IA implique généralement de lourdes dépendances natives (PyMuPDF, LibreOffice, pandoc) qui n'ont pas leur place dans un runtime JS.

AnyMD fournit une API HTTP sans état. Un appel, une réponse, du Markdown propre. Aucune dépendance native, aucune bibliothèque de formats côté serveur, aucun sous-processus Python dans votre application Node.js.

Démarrage rapide avec fetch (sans dépendance)

Node.js 18+ fournit fetch globalement. La conversion ponctuelle la plus simple :

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

C'est tout. AnyMD détecte automatiquement le format à partir de l'extension du fichier et renvoie du Markdown GitHub Flavored.

TypeScript avec réponses typées

Pour les projets TypeScript, ajoutez des types de retour appropriés et une gestion des erreurs :

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 pour les endpoints de téléversement

Voici comment connecter AnyMD à un serveur Express avec Multer pour les téléversements de fichiers :

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

Traitement par lots avec axios et nouvelles tentatives

Lorsque vous avez des centaines de documents à traiter, les appels séquentiels sont trop lents. Voici un processeur concurrent par lots utilisant 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 de gros documents

Pour les documents de plus de 10 Mo, utilisez l'endpoint de streaming afin d'éviter de mettre en mémoire toute la sortie Markdown.

Comparaison avec les bibliothèques Node.js locales

FormatBibliothèque Node.jsAnyMD
DOCXmammoth — pas de prise en charge PPTX/PDF/XLSXUne API, plus de 15 formats
PDFpdf-parse — perd les tableaux et les titresGFM, préserve la structure
PPTXpptx2md — CLI uniquement, pas d'APIAPI REST, pipeline asynchrone
XLSXxlsx — export CSV, pas de MarkdownTableaux → tableaux GFM
EPUBepub2md — non maintenu depuis 2022API active et versionnée

Performances : Node.js communique avec Rust

AnyMD s'exécute sur un backend Rust/Axum.

FormatTaille du documentAnyMD (ms)Bibliothèque locale (ms)
DOCX50 Ko (10 pages)280620 (mammoth)
PDF120 Ko (15 pages)4102,100 (pdf-parse)
PPTX200 Ko (20 diapositives)5201,800 (pptx2md)
XLSX80 Ko (5 feuilles)340950 (xlsx + formatage manuel)

De Markdown à un pipeline RAG

Une fois que votre application Node.js dispose d'un Markdown propre, l'étape suivante consiste à découper et à créer des embeddings pour la recherche vectorielle. La sortie d'AnyMD est une entrée idéale pour le découpage sémantique — les limites des titres, la structure des listes et les en-têtes de tableaux sont tous préservés.

Tarifs

OffrePages/moisTaille maximale du fichierPrix
Gratuit10010 Mo$0
Starter50025 Mo$19
Pro5,00050 Mo$99
EntrepriseSur mesureSur mesureSur mesure

Un document standard est converti en moins de 0,5 seconde. 100 pages gratuites par mois — sans carte bancaire.

Obtenez votre clé API gratuite et commencez dès aujourd'hui à convertir des documents en Markdown depuis Node.js.


← Lire la suite →