DEV Community

jake
jake

Posted on • Originally published at paperjsx.com

Generate PPTX in Hono on Bun with PaperJSX

Hono has tens of millions of weekly npm downloads and runs on every JavaScript runtime. PaperJSX generates PPTX, PDF, DOCX, and XLSX from JSON with zero native dependencies. Together: one API endpoint that generates PowerPoint decks on Bun, Node.js, Deno, or Cloudflare Workers — same code, any runtime. This tutorial starts with Bun, then shows how to deploy the same code to other runtimes with zero changes.

Why Hono + Bun?

According to Hono's npm page, it is "a small, simple, and ultrafast web framework built on Web Standards." Per its npm page, it receives roughly 47 million weekly downloads (as of July 2026). According to PkgPulse, Hono is the recommended default for new greenfield projects in 2026, replacing Express for new API development.

Hono's architecture makes it ideal for document generation endpoints: zero dependencies, sub-14KB bundle, Web Standard Request/Response objects, and the same code deploys to Bun, Node.js, Deno, Cloudflare Workers, and AWS Lambda. PaperJSX shares the same philosophy — zero native dependencies, pure JavaScript, any runtime.

Not on Hono? The framework-agnostic pattern lives in generate PPTX from any data source, and Express users should start with the Express PPTX tutorial instead.

Setup

bun create hono@latest doc-api
# Select: bun
cd doc-api
bun add @paperjsx/json-to-pptx
Enter fullscreen mode Exit fullscreen mode

The endpoint

import { Hono } from "hono";
import { generate } from "@paperjsx/json-to-pptx";

const app = new Hono();

app.get("/", (c) => c.text("doc-api running on Bun + Hono"));

app.post("/generate", async (c) => {
  const body = await c.req.json();

  // Accept an optional format (default: pptx)
  const schema = body.schema;
  if (!schema?.slides) {
    return c.json({ error: "Missing schema.slides" }, 400);
  }

  const start = performance.now();
  const buffer = await generate(schema);
  const ms = (performance.now() - start).toFixed(1);

  return new Response(buffer, {
    headers: {
      "Content-Type":
        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
      "Content-Disposition": 'attachment; filename="deck.pptx"',
      "X-Generation-Time": `${ms}ms`,
    },
  });
});

export default app;
Enter fullscreen mode Exit fullscreen mode
bun run dev
# Started server at http://localhost:3000
Enter fullscreen mode Exit fullscreen mode
curl -X POST http://localhost:3000/generate \
  -H "Content-Type: application/json" \
  -d '{
    "schema": {
      "slides": [
        {
          "elements": [
            { "type": "text", "value": "Q3 Report",
              "style": { "fontSize": 36, "bold": true } },
            { "type": "chart", "chartType": "bar",
              "data": {
                "categories": ["NA", "EMEA", "APAC"],
                "series": [
                  { "name": "Revenue", "values": [4200, 3100, 2800] }
                ]
              }
            }
          ]
        }
      ]
    }
  }' -o deck.pptx
Enter fullscreen mode Exit fullscreen mode

Open deck.pptx in PowerPoint. One slide with a title and a native editable bar chart. The X-Generation-Time header shows how long generation took — typically 20–50ms on Bun for a simple deck.

How do you serve multiple formats?

Accept a format parameter to generate PPTX, PDF, DOCX, or XLSX from the same JSON schema. This is the same multi-format pattern adapted for Hono.

import { Hono } from "hono";
import { generate as toPptx } from "@paperjsx/json-to-pptx";
import { generate as toPdf }  from "@paperjsx/json-to-pdf";
import { generate as toDocx } from "@paperjsx/json-to-docx";
import { generate as toXlsx } from "@paperjsx/json-to-xlsx";

const formats: Record<string, { fn: Function; mime: string; ext: string }> = {
  pptx: { fn: toPptx, ext: "pptx", mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
  pdf:  { fn: toPdf,  ext: "pdf",  mime: "application/pdf" },
  docx: { fn: toDocx, ext: "docx", mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
  xlsx: { fn: toXlsx, ext: "xlsx", mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
};

const app = new Hono();

app.post("/generate/:format", async (c) => {
  const fmt = formats[c.req.param("format")];
  if (!fmt) return c.json({ error: "Unsupported format" }, 400);

  const { schema } = await c.req.json();
  const buffer = await fmt.fn(schema);

  return new Response(buffer, {
    headers: {
      "Content-Type": fmt.mime,
      "Content-Disposition": `attachment; filename="output.${fmt.ext}"`,
    },
  });
});

export default app;
Enter fullscreen mode Exit fullscreen mode

Now POST /generate/pptx, POST /generate/pdf, POST /generate/docx, or POST /generate/xlsx — one API, four formats, same JSON schema.

How do you deploy to other runtimes?

The same src/index.ts deploys to every Hono-supported runtime. Only the entry point and deployment command change.

Runtime Run command Deploy
Bun bun run dev Docker, Fly.io, Railway
Node.js npx @hono/node-server Any Node host
Deno deno run --allow-net src/index.ts Deno Deploy
Cloudflare Workers wrangler dev wrangler deploy
AWS Lambda SAM local SAM deploy
Vercel vercel dev vercel

For Node.js, add the @hono/node-server adapter:

import { serve } from "@hono/node-server";
import app from "./src/index";

serve({ fetch: app.fetch, port: 3000 });
Enter fullscreen mode Exit fullscreen mode

For Cloudflare Workers, export default app is already the correct entry point — Workers use the same fetch handler that Hono exports by default. PaperJSX's PPTX engine runs within Workers' size limits. For larger format packages (PDF, DOCX, XLSX), use Bun or Node.js where bundle size is not constrained.

This runtime portability is why Hono is the recommended framework for PaperJSX API endpoints. Write once, deploy anywhere — the same code that runs on Bun locally deploys to Cloudflare Workers in production, or to a Node.js container, or to Supabase Edge Functions (which also uses Web Standard APIs via Deno).

Comparison with Express

For Express users migrating to Hono, the API is familiar but cleaner. See the Express PPTX tutorial for the Express version — the Hono version is shorter, type-safe, and deploys to more runtimes without adapter libraries.

Dimension Express Hono
TypeScript Community types (@types/express) Native (built-in)
Runtimes Node.js only Bun, Node, Deno, CF Workers, Lambda
Bundle size ~2 MB (with deps) 14 KB (zero deps)
File response res.set() + res.send() new Response(buffer, { headers })
PaperJSX integration Works Works (same code, more runtimes)

Start generating documents with Hono — read the PPTX quickstart, explore multi-format generation, or see the Express tutorial for comparison.

Top comments (0)