A microservice for converting GROWI pages to PDF format using Puppeteer.
| Component | Technology |
|---|---|
| Framework | Ts.ED 8.x (Express-based TypeScript framework) |
| PDF Generation | Puppeteer ^23.1.1 with puppeteer-cluster |
| API Documentation | Swagger via @tsed/swagger |
| Health Checks | @godaddy/terminus |
# Development
pnpm run dev:pdf-converter # Start dev server with nodemon
# Quality Checks
pnpm run lint:typecheck # TypeScript type check
pnpm run lint:biome # Biome linter
pnpm run test # Run tests
# Build & Production
pnpm run build # Build for production
pnpm run start:prod # Start production server
src/
├── index.ts # Application entry point
├── server.ts # Ts.ED server configuration
├── controllers/
│ ├── index.ts # Controller exports
│ ├── pdf.ts # PDF conversion endpoint
│ ├── pdf.spec.ts # Controller tests
│ └── terminus.ts # Health check endpoint
├── service/
│ └── pdf-convert.ts # Puppeteer PDF conversion logic
└── bin/
└── index.ts # CLI commands (swagger generation)
Controllers use Ts.ED decorators:
import { Controller, Get, Post } from '@tsed/common';
import { Returns } from '@tsed/schema';
@Controller('/pdf')
export class PdfController {
@Post('/')
@Returns(200, Buffer)
async convert(@BodyParams() body: ConvertRequest): Promise<Buffer> {
return this.pdfService.convert(body.html);
}
}
The PDF conversion service uses puppeteer-cluster for parallel processing:
// service/pdf-convert.ts
import { Cluster } from 'puppeteer-cluster';
export class PdfConvertService {
private cluster: Cluster;
async convert(html: string): Promise<Buffer> {
return this.cluster.execute(html, async ({ page, data }) => {
await page.setContent(data);
return page.pdf({ format: 'A4' });
});
}
}
Use @godaddy/terminus for Kubernetes-compatible health endpoints:
/health/liveness - Is the service alive?/health/readiness - Is the service ready to accept requests?| Method | Endpoint | Description |
|---|---|---|
| POST | /pdf |
Convert HTML to PDF |
| GET | /health/liveness |
Liveness probe |
| GET | /health/readiness |
Readiness probe |
pnpm run gen:swagger-spec
Outputs to specs/ directory.
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 3001 |
SKIP_PUPPETEER_INIT |
Skip Puppeteer initialization (for tests) | false |
Tests use Vitest with supertest for HTTP assertions:
import { describe, it, expect } from 'vitest';
import supertest from 'supertest';
describe('PdfController', () => {
it('should convert HTML to PDF', async () => {
const response = await supertest(app)
.post('/pdf')
.send({ html: '<h1>Hello</h1>' });
expect(response.status).toBe(200);
expect(response.headers['content-type']).toContain('application/pdf');
});
});
The main GROWI app (apps/app) uses @growi/pdf-converter-client package to communicate with this service:
// In apps/app
import { PdfConverterClient } from '@growi/pdf-converter-client';
const client = new PdfConverterClient('http://pdf-converter:3001');
const pdfBuffer = await client.convert(pageHtml);
pnpm run lint:typecheck # Type check
pnpm run lint:biome # Lint
pnpm run test # Run tests
pnpm run build # Verify build
This service is deployed separately from the main GROWI application and communicates via HTTP.