A TypeScript-based, Docker-powered API and CLI tool for compressing PDF files using Ghostscript with selectable compression profiles and automated cleanup.
- Compress PDFs using Ghostscript with selectable profiles:
screen,ebook,printer,prepress
- Upload and download compressed PDF via REST API
- Run compression locally using a CLI script
- File cleanup automation using cron (defaults to 5 days)
- Written in TypeScript with strong typings
- Fully Dockerized for easy deployment
- Integrated Jest + Supertest testing with coverage
Create a .env file based on the provided .env.example:
# Container name
CONTAINER_NAME=compress-pdf
# Port the server should run on
PORT=3000
# Number of days to keep uploaded files before deletion
CLEANUP_FILE_AGE_DAYS=5This project is designed to run inside a Docker container because it depends on Ghostscript.
You do not need to install Ghostscript on your host machine.
docker compose up --buildThen access the API at:
http://localhost:3000
If you'd prefer to run it without Docker, you must have Ghostscript installed manually:
- Install Ghostscript
- Run:
npm install && npm run dev
You can compress a PDF file locally using the built-in CLI script:
npm run compress -- ./original.pdf screen ./out/final.pdfThe script will:
- Check if the Docker container is already running
- Start or build the container if needed
- Wait for the API to become available (
/health) - Upload your file and save the compressed version
| Profile | Description | Target Use | Image DPI | Compression Level |
|---|---|---|---|---|
screen |
Lowest quality & size | Web viewing | ~72 DPI | π₯ Maximum |
ebook |
Medium quality | E-books | ~150 DPI | π§ High |
printer |
High quality for printing | Office print | ~300 DPI | π¨ Medium |
prepress |
Highest quality with color info | Professional print | ~300 DPI+ | π© Low |
- Method:
POST - Content-Type:
multipart/form-data - Field:
pdf(your PDF file)
Returns the compressed PDF file for download.
Below are examples for calling the compression API in various common programming environments.
curl -F "pdf=@example.pdf" "http://localhost:3000/compress?profile=screen" --output compressed.pdf<?php
$ch = curl_init();
$data = ['pdf' => new CURLFile('example.pdf', 'application/pdf')];
curl_setopt_array($ch, [
CURLOPT_URL => 'http://localhost:3000/compress?profile=ebook',
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $data,
]);
$response = curl_exec($ch);
file_put_contents('compressed.pdf', $response);
curl_close($ch);
?>import requests
files = {'pdf': open('example.pdf', 'rb')}
response = requests.post('http://localhost:3000/compress?profile=printer', files=files)
with open('compressed.pdf', 'wb') as f:
f.write(response.content)const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const form = new FormData();
form.append('pdf', fs.createReadStream('example.pdf'));
axios.post('http://localhost:3000/compress?profile=prepress', form, {
headers: form.getHeaders(),
responseType: 'stream'
}).then(res => {
res.data.pipe(fs.createWriteStream('compressed.pdf'));
});const formData = new FormData();
formData.append('pdf', fileInput.files[0]); // fileInput is a file input element
fetch('http://localhost:3000/compress?profile=ebook', {
method: 'POST',
body: formData,
})
.then(res => res.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'compressed.pdf';
a.click();
});package main
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
file, _ := os.Open("example.pdf")
defer file.Close()
var b bytes.Buffer
writer := multipart.NewWriter(&b)
part, _ := writer.CreateFormFile("pdf", "example.pdf")
io.Copy(part, file)
writer.Close()
req, _ := http.NewRequest("POST", "http://localhost:3000/compress?profile=ebook", &b)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
out, _ := os.Create("compressed.pdf")
io.Copy(out, resp.Body)
}A cron job deletes files in /uploads older than CLEANUP_FILE_AGE_DAYS. You can configure this in .env.
This project uses Jest and Supertest for API testing.
docker compose run --rm ghostscript npx jest --coverageAfter running the above command, open:
coverage/lcov-report/index.html
Mark Taborosi
π§ mark.taborosi@gmail.com
MIT