Skip to main content
Back to Portfolio

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.

Laravel 13PHP 8.3MySQLRedisHorizonTwig 3Spatie/BrowsershotPhpOffice/PhpWordFilament 5Next.js 16ScalarOpenAPI 3.1TypeScriptPython
Docgen preview

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 via GET /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/renders returns 202 with a poll URL by default; ?sync=true blocks 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 data against 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::addHtml reading 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, @page directives).

Security + delivery:

  • SSRF guard on Chromium asset fetches refuses URLs pointing at private / loopback / link-local IP ranges.
  • Signed download URLs via Laravel temporarySignedRoute with 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/keys mints a 30-minute docgen_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 pollRender ergonomics 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

Docgen screenshot 2
Docgen screenshot 3
Docgen screenshot 4

Interested in working together?

Let's discuss how I can help with your project

Get in Touch