Docgen
Document generation API — HTML+Twig templates, frozen versions, async renders, signed downloads, three output formats (HTML / PDF / DOCX) from one source, and SDKs in three languages.

Overview
A portfolio build that demonstrates the whole document-generation API surface — templates, versioning, async renders, signed downloads, three formats, three SDKs, an operator dashboard, and a deploy story — not just a PDF library wrapped in HTTP.
Templating + versioning:
- HTML + Twig source with sandboxed evaluation. Explicit allowlist of tags / filters / functions; no PHP function passthrough, no
attribute(), no file includes from arbitrary paths. Templates run as data. - AST-walking merge-field discovery. The API parses the Twig body, walks the AST, infers field types from usage (
{% for %}→ array, dotted access → object, default → scalar), and returns a JSON-schema-ish shape viaGET /v1/templates/{id}/fields. Twelve unit tests cover the edge cases — nested loops, set-locals, pseudo-vars, filter chains. - Frozen versions. Templates are mutable drafts; versions are immutable contracts. Once a version exists, the body and the fields schema are snapshotted forever. Renders pin a specific version, so last month's invoices don't change when this month's template ships.
Render lifecycle:
- Async + sync from the same endpoint.
POST /v1/rendersreturns 202 with a poll URL by default;?sync=trueblocks up to 15 seconds with a fallback to 202 if the deadline overshoots. - Idempotency keys. Same key + same template version + same input data hash returns the cached render record; same key + different parameters returns 409. Separate idempotency-records table works around MySQL's lack of partial unique indexes.
- Schema-validated input data. Renders validate the submitted
dataagainst the version's frozen field schema; missing fields return RFC-7807 problem-details responses with per-field errors before any work is queued. - Per-render observability — input data sha256, output sha256, render duration, Chromium version recorded on every successful render.
Output formats from one source:
- HTML — Twig render directly.
- PDF — Spatie/Browsershot wrapping headless Chromium via puppeteer. Per-render Chromium spawn to avoid memory accumulation in long-running workers.
- DOCX — PhpOffice/PhpWord with
Html::addHtmlreading the rendered HTML fragment. Honest fidelity story documented at/concepts/docx-fidelity— what survives the conversion (block layout, basic CSS, tables) and what doesn't (flexbox, absolute positioning,@pagedirectives).
Security + delivery:
- SSRF guard on Chromium asset fetches refuses URLs pointing at private / loopback / link-local IP ranges.
- Signed download URLs via Laravel
temporarySignedRoutewith configurable TTL — default 1 hour, capped at the workspace's max (default 24 hours). No public buckets, no auth headers on the link. - Sandbox endpoint
POST /v1/sandbox/keysmints a 30-minutedocgen_test_key, provisions a fresh workspace, and pre-seeds three reference templates. Rate-limited per IP.
Surface:
- REST endpoints under
/v1/covering templates, template versions, fields, renders, signed downloads, sandbox keys, healthz. - OpenAPI 3.1 spec hand-authored as the source of truth; controllers conform to it. CI lints with Spectral.
- Filament 5 admin dashboard with a RendersOverview widget covering renders 24h, success rate, avg duration, queue depth.
- Next.js 16 docs site at the brand domain — 21 statically rendered routes, Scalar-powered try-it console with one-click sandbox-key minting, six conceptual guides, three SDK quickstart pages.
- SDKs in TypeScript, PHP, Python. Generated from the spec, each ships a hand-tuned
pollRenderergonomics helper that backs off exponentially and respects a wall-clock budget.
Stack:
- Laravel 13, PHP 8.3, MySQL 8, Redis + Horizon under supervisord, Apache + mod_php, Let's Encrypt
- Twig 3 (sandboxed), Spatie/Browsershot + puppeteer Chromium for PDF, PhpOffice/PhpWord for DOCX
- Filament 5 for the admin dashboard
- Next.js 16, Scalar API reference, Tailwind 4
- @openapitools/openapi-generator-cli 7.22 for SDK generation
- PHPUnit for the test suite
What it proves:
The same person who hand-authored the OpenAPI spec wrote the Laravel controllers, built the Twig sandbox, wrote the AST-walking field-discovery service, wired the per-format renderers (Chromium + PhpWord), generated the SDKs, hand-tuned the polling helpers, built the Filament dashboard, scaffolded the Next.js docs site, wrote six conceptual guides, set up the EC2 deploy pipeline with its puppeteer-symlink quirks, and shipped it live. The case for hiring me to build a single coherent document-generation product instead of stitching together a backend dev + frontend dev + DevOps + tech writer.
Results
61 PHPUnit tests, all passing — field discovery edge cases, render lifecycle, PDF + DOCX render smoke, SSRF guard
21 statically rendered docs routes with six conceptual guides and a Scalar try-it console
Three SDKs (TypeScript / PHP / Python) from one OpenAPI spec, each with a hand-tuned pollRender helper
Sync render of HTML + PDF + DOCX in 2.76s on the live EC2 host
Atomic release-based deploy via SSH + rsync, zero-downtime symlink switch
Gallery


