I needed to render Markdown in a project but didn't want to bundle a parser. A serverless function felt like overkill for what should be a curl away. Here's what I did.
The Worker
import { marked } from 'marked';
export default {
async fetch(request) {
if (request.method !== 'POST')
return new Response('Send POST', { status: 405 });
const { text } = await request.json();
const html = await marked.parse(text);
return new Response(JSON.stringify({ html }), {
headers: {
'content-type': 'application/json',
'access-control-allow-origin': '*'
}
});
}
};
That's it. One file, one dependency (marked), one export.
The Build
I bundled with esbuild so the Worker is self-contained:
npx esbuild worker.js --bundle --minify --outfile=dist/worker.js
The output is ~54KB (most of it is marked). Then:
npx wrangler deploy
Total time from mkdir to live endpoint: about 10 minutes.
Quick Test
curl -X POST https://md-to-html.sydseriousman.workers.dev \
-H "Content-Type: application/json" \
-d '{"text": "# Hello\n\n**bold** and *italic*"}'
# {"html":"<h1>Hello</h1>\n<p><strong>bold</strong> and <em>italic</em></p>"}
Why This Approach
- No cold starts (Workers runs V8 isolates)
- Free tier: 100k requests/day
- No server to patch or maintain
- CORS enabled for browser use
The Worker URL is directly usable, or I published it through RapidAPI for discoverability and subscription management.
One thing I'd do differently next time: add streaming response support. marked.parse() returns a string, but for large documents, sending chunks as they render would be better UX.
Top comments (0)