Back to our work

Serverless file converter

An image and document converter on AWS, with a free guest demo.

Built by our co-founder. Will Soltani built it as his own project and public demo, not for a client. The code is public.

The converter’s guest workspace at soltani.org/test, at rest: an empty drop zone, the accepted file types and the conversion settings.
The guest workspace at soltani.org/test.

Layers involved: Security and reliability, Cloud and platform and Back end and APIs

Built with: AWS Lambda (container image), Step Functions, S3, DynamoDB, KMS, Cognito, CloudWatch, AWS CDK, GitHub Actions, Next.js on App Runner, sharp, Pillow, PyMuPDF, LibreOffice

Try it at soltani.org/testSource on GitHub

What it does

It converts images, PDFs and Word or Pages documents between formats. Images convert to 12 formats, including PNG, JPG, WebP, AVIF, HEIC and PDF. A PDF becomes images, one per page, zipped when there are several. Word and Pages files become a PDF or images.

Each file gets its own target format, quality and resize settings, or a preset such as WebP at quality 80 for the web.

Anyone can try it as a guest at soltani.org/test, with five conversions per session, the same formats and the same pipeline. Signing in raises the limit to ten and adds a PDF form-fill editor.

How it works

  1. The browser asks the API for an upload. The API answers with a signed S3 link that lasts 300 seconds, with the file’s content type signed in.
  2. The browser sends the file straight to a private S3 bucket. A completion step reads the first 64 KB, detects the real type from the bytes, rejects unsupported files and records the file in DynamoDB.
  3. A convert request carries up to 25 conversions. Each one is checked: the file exists, belongs to the caller and can become the chosen format. Then it reserves one unit of quota and starts its own Step Functions execution, four at a time.
  4. Step Functions runs a single task: a container Lambda with Node 20, Python 3 and LibreOffice inside. sharp does most image work; Pillow handles HEIC, HEIF, AVIF, BMP and ICO; PyMuPDF turns PDF pages into images; LibreOffice turns Word files into PDFs. A Pages file becomes a PDF from the preview stored inside it.
  5. Results go to a separate encrypted bucket and their status to DynamoDB. The page checks every 2 seconds, backing off to 10, while anything is still processing.
  6. Downloads use a fresh signed link that lasts 60 seconds.
  7. Sign-in is Amazon Cognito with the authorization-code flow and PKCE. Each guest session gets its own isolated space.
  8. Storage, the worker and the workflow are defined in the AWS CDK and deploy from GitHub Actions through OIDC. The Next.js front end runs on AWS App Runner.

Decisions and tradeoffs

Upload straight to S3

Files go from the browser to S3 by a signed link, never through the web server. The tradeoff: S3 does not enforce the 250 MB cap here, so the cap is checked on the size the browser declares when the link is issued.

Trust the bytes, not the name

The real type comes from the file’s first bytes, so a renamed file is caught. Unsupported types are turned away with a 415.

Step Functions around one Lambda

A workflow with a single task looks like extra machinery, but it adds automatic retries on Lambda service errors, with growing waits between tries, and a five-minute timeout. The worker has no retry code of its own.

One container for every engine

Node, Python and LibreOffice ship in one image, so one worker handles every format. It runs with a fixed 2,048 MB and a two-minute timeout. Cold starts were not measured.

Reserve quota first

One conditional DynamoDB update reserves a unit before the job starts, so parallel requests can’t overrun the limit. The cost: if starting the job fails, that unit is not refunded.

Pages files from their own preview

A .pages file carries a preview PDF or preview images. The converter uses those instead of a full Pages renderer, so the output is the preview, not a full layout render.

Security

What can go wrong

Next step

Try it as a guest at soltani.org/test, or read the code on GitHub. To build something like it, start a project.

Start a project

Next case study: Scroll-film pipeline