Architecture
Repository layout
Section titled “Repository layout”The repository is an npm workspaces monorepo (Node.js 22 or newer).
- package.json root scripts (dev, build, start, test, lint, teams:package)
Directorypackages/
Directoryshared/
@pdfeditor/shared: brand, plans and prices, roles, activity actions, field types, e-sign consent text- …
Directoryapps/
Directoryapi/
@pdfeditor/api: Express API, Postgres access, migrations, scriptsDirectorymigrations/
Directorycore/ application tables
- …
Directoryaudit/ the write-only activity log
- …
- sql/audit-roles.sql insert-only and read-only roles for the audit log
Directoryscripts/ dev-db, migrate, create-super-admin, stripe-setup
- …
Directoryweb/
@pdfeditor/web: the React app (editor, signing, admin, platform), built with Vite- …
Directorydocs/
@pdfeditor/docs: this site (Astro Starlight)- …
Directorymarketing/
@pdfeditor/marketing: the marketing website (Astro)- …
Directoryteams/
- build-package.mjs builds the Microsoft Teams app package
How it runs
Section titled “How it runs” Browser / Microsoft Teams │ HTTPS (one origin) ▼ API (Node.js, Express) ── serves apps/web/dist and /api/* │ │ │ │ │ │ │ └─ Mailgun (platform email) │ │ └─ Stripe (checkout, portal, webhooks) │ └─ Microsoft Entra ID + Microsoft Graph (on behalf of the user) ▼ PostgreSQL ── core data, documents sent for signature └── audit.events (optionally a separate database)- One origin. In production the API serves the built web app (
apps/web/dist) as well as/api. Same-origin means no CORS and no third-party cookies, which matters inside the Teams iframe. Hashed files under/assets/are cached for a year; everything else is served withno-cache. Any non-API path returnsindex.htmlso client-side routes work. - Sessions are tokens sent in the
Authorizationheader (not cookies), stored in the browser’s local storage outside Teams and in memory inside Teams. - Microsoft access is delegated: the browser sends the user’s Entra token for this API, and the API exchanges it for a Microsoft Graph token (OAuth 2.0 on-behalf-of) for each call. See Security model.
- PostgreSQL holds organizations, users, sessions, signing requests and their PDFs. The activity log lives in the
auditschema, in the same database or a separate one. See Database and audit log. - A background job in the API process runs hourly: it expires overdue signing requests and sends automatic reminders.
- The docs and marketing sites are static builds (
apps/docs/dist,apps/marketing/dist). Where you host them is your choice.
Root scripts
Section titled “Root scripts”| Script | What it does |
|---|---|
npm run dev |
API (--watch, port 3001) and web app (Vite, port 5173) together |
npm run dev:db |
Local embedded Postgres for development |
npm run build |
Builds the web app, the docs and the marketing site |
npm start |
Starts the API (which serves the built web app) |
npm test |
Tests for @pdfeditor/shared and the API (the API tests start their own embedded Postgres) |
npm run lint |
Lints the web app and the API |
npm run teams:package |
Builds the Teams app package |
API workspace scripts (npm run <script> -w @pdfeditor/api): dev, start, db:dev, migrate, create-super-admin, stripe:setup, test, lint.