Fragments

A cloud microservice on AWS

A containerized Node microservice for storing and converting small pieces of text, JSON, and images — deployed to ECS Fargate behind a load balancer, with metadata in DynamoDB, blobs in S3, Cognito for auth, and a tagged release pipeline that ships it.

Node.jsExpress 5DockerAWS ECS FargateAmazon ECRDynamoDBAmazon S3Amazon CognitoGitHub ActionsJestHurlsharp
Coursework project — the repository is private and the AWS resources have been torn down.

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.

11
Supported fragment types
2
Auth strategies, one codebase
80%+
Unit test coverage
14
Hurl integration suites

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.

Fragments request path on AWSfragments-uibrowser clientAmazon Cognitouser pool + hosted UIApplicationLoad BalancerECS Fargatefragments containerExpress · Node 24 alpineDynamoDBfragment metadataS3fragment blobsCloudWatch Logspino → awslogs driver1 · sign in2 · Bearer token3verify JWTmetadatadataOne record per fragment, split across two stores: small metadata in DynamoDB, the bytes in S3.
The request path. Sign-in happens against Cognito before the API is touched; the container verifies the token itself rather than delegating to the load balancer.

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.
Fragments CI and CD pipelinesCI · ON EVERY PUSH TO MAINESLint+ PrettierhadolintDockerfile lintJest + Supertestunit tests · 80%+Hurlintegration testsdocker composeDynamoDB Local +MiniStack (S3)CD · ONLY ON A v* GIT TAGdocker buildxmulti-stage buildAmazon ECRtagged + latestTask definitionrendered with secretsECS service deploywaits for stabilityTests never touch real AWS — the same code runs against local DynamoDB and S3 stand-ins in CI.
CI on every push, CD only on a version tag. Tagging an already-tested commit is what makes a deploy a decision rather than an accident.

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.