Fragments is a cloud microservice built over a semester for a fictional manufacturing company: IoT devices, mobile apps, and assembly-line cameras all need to store small pieces of text and images, in a dozen formats, and read them back in whichever format the consumer happens to want.
The problem it solves
A fragment is smaller than a document — a sensor reading in CSV, a status update in Markdown, a photo of a damaged part. The requirement that shaped the whole design was conversion: a Markdown fragment has to be retrievable as HTML, a JPEG as a PNG, a CSV as JSON — without storing a second copy. Conversion therefore happens on read, from the single stored original, and costs nothing in storage.
How it runs on AWS
The service runs as a Fargate task behind an Application Load Balancer. Requests carry a Cognito identity token that the server verifies itself with aws-jwt-verify, so there's no session state to keep anywhere. Storage is deliberately split: fragment metadata — id, owner, type, size, timestamps — goes to DynamoDB keyed by owner and id, while the actual bytes go to S3 under an owner-scoped key. Metadata queries stay fast and cheap, and the blobs live somewhere built to hold them.
One codebase, two backends
The data layer sits behind six functions — read and write for metadata, read and write for data, list, and delete. One implementation talks to DynamoDB and S3; another keeps everything in memory. Which one loads is decided by environment variables at startup, so the same server runs locally with no AWS account at all, and the model code above it never knows the difference.
That indirection is what makes the tests honest. Integration tests run the real container against DynamoDB Local and a MiniStack S3 stand-in via Docker Compose, exercising the actual AWS code paths — the SDK calls, the key layout, the error handling — without a single real AWS resource or a cent of spend.
Shipping it
- Every push to main runs ESLint, hadolint against the Dockerfile, Jest and Supertest unit tests above 80% coverage, and the full Hurl integration suite against the composed stack.
- The image is a multi-stage build on node:24-alpine — production dependencies installed in a throwaway stage, only the resulting node_modules and source copied into the final layer, with a HEALTHCHECK hitting the service's own health route.
- Deployment is gated on intent, not on merging: only pushing a v* git tag triggers CD, which builds the image, pushes it to ECR under both the version tag and latest, renders the ECS task definition with secrets injected from GitHub, and rolls the service — waiting for stability before it reports success.
Conversion
Every supported type declares which extensions it can be read as, in one table. A request for a fragment with an extension looks up that table, and either runs the conversion — markdown-it for Markdown to HTML, js-yaml for JSON to YAML, csv-parse for CSV to JSON, sharp for anything image to anything image — or returns a 415 explaining that the conversion isn't possible. Adding a format later means adding a row, not rewriting the route.
What I took from it
The interesting part of this project was never the API surface — CRUD over text is not hard. It was everything around it: making a service that behaves identically on a laptop and on Fargate, proving it with tests that don't need the cloud to run, and building a pipeline where the risky step is the one you have to opt into. Those constraints are what the AWS parts were really teaching.