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
| Muoto | Node.js-kirjasto | AnyMD |
|---|---|---|
| DOCX | mammoth — ei PPTX/PDF/XLSX-tukea | Yksi API, yli 15 formaattia |
pdf-parse — menettää taulukot ja otsikot | GFM, säilyttää rakenteen | |
| PPTX | pptx2md — vain CLI, ei API:a | REST API, asynkroninen pipeline |
| XLSX | xlsx — CSV-vienti, ei Markdownia | Taulukot → GFM-taulukot |
| EPUB | epub2md — ei ylläpidetty vuodesta 2022 | Aktiivinen, versioitu API |
Suorituskyky: Node.js kommunikoi Rustin kanssa
AnyMD toimii Rust/Axum-backendillä.
| Muoto | Asiakirjan koko | AnyMD (ms) | Paikallinen kirjasto (ms) |
|---|---|---|---|
| DOCX | 50 kt (10 sivua) | 280 | 620 (mammoth) |
| 120 kt (15 sivua) | 410 | 2,100 (pdf-parse) | |
| PPTX | 200 kt (20 diaa) | 520 | 1,800 (pptx2md) |
| XLSX | 80 kt (5 taulukkoa) | 340 | 950 (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
| Paketti | Sivua/kuukausi | Tiedoston enimmäiskoko | Hinta |
|---|---|---|---|
| Ilmainen | 100 | 10 Mt | $0 |
| Starter | 500 | 25 Mt | $19 |
| Pro | 5,000 | 50 Mt | $99 |
| Enterprise | Mukautettu | Mukautettu | Mukautettu |
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.