Projects

ProjectDeveloper Tools · 2026

PDF Everything

A unified PDF console and REST API with nineteen tools for conversion, rendering, extraction, and document workflows.

  • Next.js
  • NestJS
  • TypeScript
  • Turborepo
  • Chromium
One shared schema package feeds a static console and a NestJS gateway that routes to workers grouped by runtimetypes/ packageone answer to valid inputConsolea static exportNO PDF CODENestJS gatewayversioned /api/v1uploadsvalidationroutingfile idspdf-core-workerport 8020pdf-lib and sharpChromium workerport 8010own imageResultdownload or a file refONE SCHEMACALLSSTREAMSGROUPED BY RUNTIME, NOT BY OPERATIONONE IMAGE FOR SEVENTEEN OPERATIONS

Developer Tools · Project

Chapter
Applied AI and documents
Year
Availability
Open source
Last commit

PDF Everything puts nineteen document operations behind one visual console and one versioned REST API. It covers PDF manipulation, image and text work, form filling, and HTML or Markdown rendering. Anything I can do by clicking in the console, an integration can do over the same public endpoint.

Building a browser tool and an API usually means writing each operation twice, once per surface. The two copies then drift, and the console starts accepting a file the API would reject. I did not want to maintain two answers to the same question about what a valid input is.

 pnpm install pnpm devconsole      http://localhost:3000/pdf-everything/console/nest api     http://localhost:3001/apiswagger ui   http://localhost:3001/api/docspdf worker   http://localhost:8020chromium     http://localhost:8010 curl -sI localhost:3001/api/docs | head -1HTTP/1.1 200 OK
PDF Everything in use

Where the contract lives

The types package holds Zod schemas, and both the console and the backend import them. The console is a static export with no API routes and no PDF implementation inside it. NEXT_PUBLIC_API_ORIGIN selects the Nest origin at build time, so every control in the console calls exactly the endpoint an integration would call. That is the property I was after. There is one answer to what a valid input is, and one place to change it. console/lib/tools/ keeps a module and a form per operation on top of those shared schemas, so the interface stays declarative rather than reimplementing the rules.

Gateway and workers

NestJS owns the versioned /api/v1 contract. Multipart uploads, stored file references, validation and problem responses, file persistence, routing to the right worker, and streaming the result back. It imports neither pdf-lib, nor pdfjs-dist, nor sharp, nor Chromium, so a native document dependency cannot fail inside the public API process. pdf-core-worker listens on port 8020 and accepts a private POST /v1/execute/:operation. The Chromium worker listens on 8010 and serves /v1/render/html and /v1/render/markdown. Chromium keeps its own lockfile and its own image, because its size, cold starts, memory profile and security boundary have nothing in common with pdf-lib.

Calling the API

Upload once, then address the file by id, so a second operation does not resend the bytes.

curl -X POST "$API_ORIGIN/api/v1/files" -F "[email protected]"
curl -X POST "$API_ORIGIN/api/v1/organize/rotate?output=ref" \
  -H 'content-type: application/json' \
  -d '{"fileIds":["FILE_ID"],"options":{"angle":90}}'

Page ranges are one-based strings such as 1-3,5,7-9. Out-of-range pages are ignored, while a reversed range or page zero is invalid. The default response is a binary download, and ?output=ref returns FileMeta JSON instead. Split returns a list of files, forms extract returns field descriptions, and text extraction returns text/plain with an X-Page-Count header. Uploaded files and fileIds cannot be mixed in one request.

Decisions and costs

  • Group workers by runtime rather than by operation. Seventeen operations share one Node.js image because they share the same pdf-lib, pdfjs-dist and sharp profile. Chromium is separate for exactly the same reason inverted. One service per operation was rejected on purpose.
  • Keep the console a static export. It ships to a CDN, holds no secrets, and cannot do anything the public API does not already expose. The cost is that the console cannot optimize a flow by shortcutting the gateway.
  • Send file bytes to the core worker as base64 inside the internal JSON protocol. It is simple and synchronous, and it caps request size and rules out a clean Lambda deployment for core operations. That is the decision I would revisit, in favour of signed object-storage references.

Limits and security

Documents and HTML are treated as untrusted. Execution routes stay private or authenticated, worker tokens never reach the console, and Chromium runs with --no-sandbox inside its container, so platform isolation carries that weight. The Chromium worker currently allows remote images, fonts and stylesheets, which wants an egress allowlist before it accepts arbitrary multi-tenant HTML. Lambda suits small HTML and Markdown jobs today, while core operations need the object-storage protocol first. Worker images publish to ghcr.io, and production should pin a digest rather than latest. It is open source, with Swagger served next to the API and a Fumadocs site alongside the console. Before pushing I run pnpm lint && pnpm typecheck && pnpm build && pnpm test.

Next projectVoice AI

The Noir Game

View project