AWS in Production: Wiring S3, SES, SQS and EventBridge into NestJS
A healthcare platform leans on a lot of AWS. Here is how S3, SES, SQS, Rekognition and EventBridge Scheduler fit into a NestJS API — what each one is actually for, and how to keep them testable.
AWS in Production: Wiring S3, SES, SQS and EventBridge into NestJS
On this page
EMPATHIKA — the enterprise healthcare platform I work on — leans on AWS for the heavy, undifferentiated work: storage, email, queues, image analysis, and scheduling. The trick is using each managed service for what it is genuinely good at, while keeping your own code from hard-coupling to the SDK everywhere. Here is how the pieces fit in a NestJS API.
S3: never let bytes touch your API
User uploads (documents, images) go straight to S3 via pre-signed URLs. The API mints a short-lived URL; the client uploads directly to S3; my server only stores the resulting object key. Large files never transit the NestJS process, so a 50 MB upload costs the API a few milliseconds, not a stalled request worker.
const url = await s3.getSignedUrlPromise('putObject', {
Bucket: env.UPLOAD_BUCKET,
Key: `patients/${id}/${randomUUID()}`,
Expires: 60,
});
// client PUTs the file to `url`; we persist only the keySES for email, SQS for everything that can wait
SES sends the transactional email (verification, alerts). But sending should never block the request that triggered it — so the work goes onto SQS. The API drops a message and returns immediately; a consumer drains the queue and calls SES. If SES is briefly throttled, messages wait in the queue instead of failing the user’s action. SQS also gives retries and a dead-letter queue for free.
Rekognition and EventBridge Scheduler
Rekognition handles image analysis (moderation, detection) without us training or hosting a model. EventBridge Scheduler runs the time-based work — reminders, periodic syncs — as managed cron, so there is no always-on timer process to babysit; AWS invokes the target when it is due.
Keep the SDK behind a boundary
The one rule that keeps all of this maintainable: no controller or domain service imports the AWS SDK directly. Each capability hides behind a small NestJS provider — StorageService, MailService, QueueService. Business code calls storage.put(...), not S3:
@Injectable()
export class StorageService {
async signedUploadUrl(key: string) { /* ...S3 here only... */ }
}That boundary means tests mock one interface instead of the SDK, and swapping or wrapping a service (add caching, change buckets) touches one file. It is the difference between using AWS and being married to it.
The throughline
Managed services are leverage: offload storage, email, queues, ML and scheduling to AWS and spend your time on the domain. Just keep the integration at the edges — direct-to-S3 uploads, async-via-SQS side effects, and a thin provider per service — and the platform stays fast, reliable, and testable.
Comments
Loading comments…