Blogi

Asiakirjojen muuntaminen Markdowniksi Node.js:llä

14. elokuuta 2026 · 6 min lukuaika


Jos rakennat Node.js-sovellusta, joka käsittelee käyttäjien lataamia asiakirjoja, olet luultavasti katsonut kansiota täynnä PDF-, DOCX- ja PPTX-tiedostoja ja miettinyt: miten normalisoin kaiken tämän muotoon, jota LLM-putkeni voi oikeasti käyttää?

Tässä artikkelissa käydään läpi AnyMD:n REST API:n käyttö Node.js:stä — mukana fetch, axios, TypeScript-tyypit, streaming ja Express middleware -mallit tuotantotason asiakirjasta Markdowniin -muunnokseen.

Miksi Markdownia Node.js:stä?

Node.js-sovellukset käsittelevät tiedostolatauksia luontevasti — Multer, Busboy, Express middleware. Latausten muuttaminen puhtaaksi, rakenteiseksi tekstiksi RAG-putkia tai tekoälyn koulutusdataa varten tarkoittaa kuitenkin usein raskaita natiivisidoksia (PyMuPDF, LibreOffice, pandoc), jotka eivät kuulu JS-runtimeen.

AnyMD tarjoaa tilattoman HTTP-API:n. Yksi kutsu, yksi vastaus, puhdas Markdown. Ei natiiviriippuvuuksia, ei palvelinpuolen formaattikirjastoja, ei Python-aliprosesseja Node.js-sovelluksessa.

Pika-aloitus fetchillä (ei riippuvuuksia)

Node.js 18+ tarjoaa fetch globaalisti. Yksinkertaisin kertamuunnos:

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

Siinä kaikki. AnyMD tunnistaa muodon automaattisesti tiedostopäätteestä ja palauttaa GitHub-Flavored Markdownia.

TypeScript tyypitetyillä vastauksilla

Lisää TypeScript-projekteihin asianmukaiset palautustyypit ja virheenkäsittely:

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

Express middleware upload-endpointeille

Näin liität AnyMD:n Express-palvelimeen ja käytät Multeria tiedostolatauksiin:

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

Eräkäsittely axiosilla ja uudelleenyrityksillä

Kun käsiteltäviä asiakirjoja on satoja, peräkkäiset kutsut ovat liian hitaita. Tässä on rinnakkainen eräkäsittelijä axiosilla:

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

Suurten asiakirjojen streaming

Yli 10 Mt:n asiakirjoille kannattaa käyttää streaming-endpointia, jotta koko Markdown-tulosta ei tarvitse puskuroida muistiin.

Vertailu paikallisiin Node.js-kirjastoihin

MuotoNode.js-kirjastoAnyMD
DOCXmammoth — ei PPTX/PDF/XLSX-tukeaYksi API, yli 15 formaattia
PDFpdf-parse — menettää taulukot ja otsikotGFM, säilyttää rakenteen
PPTXpptx2md — vain CLI, ei API:aREST API, asynkroninen pipeline
XLSXxlsx — CSV-vienti, ei MarkdowniaTaulukot → GFM-taulukot
EPUBepub2md — ei ylläpidetty vuodesta 2022Aktiivinen, versioitu API

Suorituskyky: Node.js kommunikoi Rustin kanssa

AnyMD toimii Rust/Axum-backendillä.

MuotoAsiakirjan kokoAnyMD (ms)Paikallinen kirjasto (ms)
DOCX50 kt (10 sivua)280620 (mammoth)
PDF120 kt (15 sivua)4102,100 (pdf-parse)
PPTX200 kt (20 diaa)5201,800 (pptx2md)
XLSX80 kt (5 taulukkoa)340950 (xlsx + manuaalinen muotoilu)

Markdownista RAG-putkeen

Kun Node.js-sovelluksellasi on puhdas Markdown, seuraava vaihe on chunking ja embedding vektorihakua varten. AnyMD:n tulos on ihanteellinen syöte semanttiseen chunkingiin — otsikkorajat, listarakenne ja taulukoiden otsikot säilyvät.

Hinnoittelu

PakettiSivua/kuukausiTiedoston enimmäiskokoHinta
Ilmainen10010 Mt$0
Starter50025 Mt$19
Pro5,00050 Mt$99
EnterpriseMukautettuMukautettuMukautettu

Tyypillinen asiakirja muunnetaan alle 0,5 sekunnissa. 100 ilmaista sivua kuukaudessa — luottokorttia ei tarvita.

Hanki ilmainen API-avaimesi ja aloita asiakirjojen muuntaminen Markdowniksi Node.js:stä jo tänään.


← Lue lisää →