
Highlights#
- Cut per-participant document prep from ~20 minutes to ~2 for an Italian Erasmus+ nonprofit by building a self-serve DOCX generation app (FastAPI, React, PostgreSQL), now live and in daily use.
- Built a template pipeline that parses uploaded Word files, extracts their variables, and infers field types (date, email, phone, decimal) from naming conventions, so staff get a validated web form without a developer in the loop.
- Eliminated the copy-paste errors that used to reach issued certificates and agreements, with per-field validation on every generated form.
How it works#
flowchart TD
subgraph reg["Register a template"]
U["Staff uploads .docx"] --> V["MIME + size validation"]
V --> X["Variable extraction
docxtpl"]
X --> Y["Type inference
from variable names"]
Y --> DB[("PostgreSQL
template + field metadata")]
V --> ST[("Local disk / S3")]
end
subgraph gen["Generate a document"]
DB --> F["Dynamic React form
pickers, placeholders, validation"]
F --> G["Server-side validation
+ injection sanitization"]
ST --> H["SHA-256 integrity check"]
G --> R["Render .docx"]
H --> R
R --> DL["Download"]
R --> A[("Audit log:
user, IP, duration, status")]
end
The claim in one picture: the metadata extracted at upload is the single source of truth the form is built from, so a new document type is a staff upload, not a code change — the red edge marks the developer who is no longer needed.
Portfolio Case Study#
Problem. AMFI International is a small nonprofit in Avezzano, Italy that runs Erasmus+ mobility and training programs. Every participant needs a stack of official documents: certificates, agreements, letters. Staff prepared each one by hand in Word, retyping names and dates per person, which took around 20 minutes per participant. The process bred exactly the failures you’d expect: typos in issued documents, stale fields left over from the previous participant, several competing copies of “the” template circulating by email, and layouts breaking as different people edited the same file in different Word versions.
Approach. Instead of hardcoding AMFI’s documents into the app, I made templates first-class: staff keep authoring them in Word, using simple {{ placeholder }} variables, and upload the file. The backend validates it (real MIME detection via libmagic, size cap, SHA-256 hash stored for later integrity checks), extracts the variables, and infers each field’s type from its name — participant_email becomes an email input, start_date a date picker, fee_amount a decimal field. The React frontend builds its form entirely from that metadata, so a new document type needs zero code changes.
I chose FastAPI and React because I have familiarity with this stack, and with a real client waiting, shipping something dependable mattered more than experimenting. The backend is organized in feature slices (auth, users, templates, documents), each with its own model, repository, service, and router, and the TypeScript client is generated from the OpenAPI schema so the frontend can’t drift from the API. Storage sits behind a small Protocol interface with local-disk and S3 implementations, which kept development friction low without a rewrite for production.
What was hard. The technical core was getting from a raw .docx to a form a non-technical person can trust: variable extraction, type inference that guesses right often enough to be useful, and a dynamic form that feels hand-built rather than generated. But the part I’m most proud of isn’t one module. It’s the communication with the client and making the app as user-friendly and user-tailored as possible — the whole point was that an 8-person team with no developer on staff can register a new document type themselves and never call me.
Outcome. The app is live and AMFI’s staff use it in their normal workflow. Once a template is registered, producing a participant’s document takes around 2 minutes instead of around 20, and the type checking and date picking catch the errors that used to end up in issued documents. Every generation is logged with the requesting user, IP, duration, and outcome, so there’s a record of what was issued. Server code is fully typed and passes strict mypy; the core user flows are covered by Playwright end-to-end tests.
Non-goals. It outputs DOCX only — no PDF export, and no e-signing or sending; distribution stays with the staff. Templates are authored in Word, not edited in-app. Access control is a simple owner/public model rather than per-department roles, which is the right size for an eight-person team.
Screenshots#


.docx, and the app takes it from there — no developer, no code change for a new document type.
student_email reads as email, the *__date fields as dates, student_phone as phone. The preview shows what it found.

Stack#
Python, FastAPI, SQLModel, PostgreSQL, Alembic, docxtpl, boto3/S3, React 19, TypeScript, TanStack Router, TanStack Query, Tailwind, shadcn/ui, Playwright, Docker Compose, Traefik, Sentry.
