ConvertoAPI is a Fastify service that turns HTML or a URL into a PDF using Playwright / Chromium.
- Node
>=24 - pnpm (pinned via
packageManagerinpackage.json)
pnpm install
pnpm exec playwright install --with-deps chromium # browser for PDF generation
cp .env.template .env # then fill in the values
pnpm devSee server-configuration.md for the environment variables.
| Script | Description |
|---|---|
pnpm dev |
Run the app with tsx |
pnpm build |
Type-check and emit to dist/ |
pnpm start |
Run the built app from dist/ |
pnpm lint |
ESLint (--max-warnings 0) |
pnpm format |
Format with Prettier |
pnpm typecheck |
tsc --noEmit |
pnpm test |
Unit tests (excludes the integration suite) |
pnpm test:integration |
Integration tests (needs a Playwright browser) |
pnpm test:coverage |
Unit tests with coverage |
The code is feature-sliced under src/features/<feature>/. Each feature exposes
a register* function from its index.ts, and buildApp() in
src/app.ts composes them — registration order matters:
ratelimit → openapi → auth → pdf → health → homepage
| Feature | Responsibility |
|---|---|
ratelimit |
Global rate limiting (@fastify/rate-limit) |
openapi |
Swagger + Scalar API reference served at /openapi |
auth |
OAuth2 client-credentials: /token and /.well-known/openid-configuration |
pdf |
POST /v1/pdf — HTML/URL → PDF, SSRF-guarded, behind JWT |
health |
GET /healthz |
homepage |
Static files (@fastify/static) |
- Authentication —
POST /tokenwith HTTP Basic (client_id:client_secret) returns a 1-hour JWT.POST /v1/pdfis protected by theverifyJWTpreHandler. - SSRF protection — when generating a PDF from a URL,
urlValidatorenforces http/https only, resolves DNS (both A and AAAA records to defeat rebinding), and rejects private, loopback, and link-local addresses (including the cloud metadata endpoint169.254.169.254). - Browser — a single Chromium instance is shared; each request gets its own
BrowserContext, and the browser is closed gracefully via Fastify'sonClosehook.
-
Unit tests live next to the code (e.g.
urlValidator.test.ts). -
Integration tests (
src/integration.test.ts) boot the app viaapp.inject()and generate a real PDF, so they need a Chromium browser:pnpm exec playwright install --with-deps chromium pnpm test:integration
GitHub Actions:
- CI (
.github/workflows/ci.yml) — on every push/PR tomain: lint, format check, typecheck, unit tests, build, and the integration suite. - Docker (
.github/workflows/docker.yml) — builds and publishes the container image. See docker.md for build instructions and the image tag scheme. - Release (
.github/workflows/release.yml) — Changesets-based releases.
Dependency updates are managed by Dependabot for npm, GitHub Actions, and the Docker base image.
Versioning and changelog are managed with Changesets:
- Include a changeset with any release-worthy change:
pnpm changeset(pick the bump type, write a summary) and commit the file in.changeset/. - When changesets land on
main, the Release workflow opens/updates a "Version Packages" PR that bumps the version and updatesCHANGELOG.md. - Merging that PR tags
v<version>, creates a GitHub Release, and triggers the Docker workflow to publishlosolio/converto:<version>+latest.
The package is private and is never published to npm — Changesets only drives
the version, changelog, and release tag.