A serverless project that securely delivers a private PDF resume using AWS Lambda, S3, and API Gateway. Built with Python to fetch, encode, and serve the file through a public endpoint without exposing the S3 bucket.
| Category | Technologies Used |
|---|---|
| Cloud Services | AWS S3, AWS Lambda, AWS API Gateway |
| Programming Language | Python 3 |
| AWS SDK | boto3 |
| Security / Access | IAM (Least Privilege Role for S3 Access) |
| Architecture Style | Serverless Architecture |
- Architect a serverless backend using AWS Lambda and API Gateway.
- Securely store a resume PDF in a private Amazon S3 bucket.
- Solve the technical challenge of serving binary files (PDFs) via a JSON-based API.
- Implement Python logic to fetch and Base64 encode files for HTTP transmission.
- Verify the public API endpoint via web browser.
- Created a unique S3 bucket (e.g.,
janmarc-resume-storage). - Uploaded
resume.pdfto the bucket. - Security Configuration: Blocked all public access to the bucket to ensure the file is only accessible via the API, not the public internet.
- Created a Python 3.x Lambda function.
- Assigned an IAM Role with specific permissions (
s3:GetObject) to read only from the resume bucket. - Wrote a Python script using
boto3to:- Fetch the PDF object from S3.
- Convert the binary PDF data into a Base64 string (essential for API Gateway transmission).
- Return a JSON response with the header
Content-Type: application/pdfandisBase64Encoded: true.
- Configured an HTTP API in API Gateway.
- Created a
GET /resumeroute triggered by the Lambda function. - Enabled CORS (Cross-Origin Resource Sharing) to allow browsers to request the file.
- Deployed the API to a production stage to generate a public Invoke URL.
- Tested the API using
curlto inspect headers. - Accessed the API Gateway URL in a web browser to confirm the PDF renders/downloads correctly.
# Verify file exists in bucket
aws s3 ls s3://janmarc-resume-storage
# Test the API functionality via terminal
curl -v https://<api-id>.execute-api.<region>[.amazonaws.com/resume](https://.amazonaws.com/resume)
import boto3
import base64
import os
def lambda_handler(event, context):
s3 = boto3.client('s3')
bucket = os.environ['BUCKET_NAME']
key = 'resume.pdf'
# Fetch and encode
file_content = s3.get_object(Bucket=bucket, Key=key)['Body'].read()
encoded_pdf = base64.b64encode(file_content).decode('utf-8')
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/pdf',
'Content-Disposition': 'inline; filename="resume.pdf"'
},
'body': encoded_pdf,
'isBase64Encoded': True
}
- Binary Data Handling: API Gateway and Lambda usually exchange JSON strings. To serve a PDF, I learned I had to Base64 encode the binary data in Python and explicitly tell API Gateway
isBase64Encoded: Trueso it decodes it back to a binary file for the user. - Least Privilege Security: Instead of making the S3 bucket public (which is risky), I used an IAM Role. This ensures only my specific Lambda function can read the resume, maintaining strict security boundaries.
- CORS: I initially encountered errors when calling the API from a browser fetch. I learned that enabling CORS headers (
Access-Control-Allow-Origin) in the response is mandatory for web integration. - Cost Efficiency: Because this uses Serverless (Lambda/S3), I effectively pay nothing ($0.00) while the resume is idle, only incurring costs when the API is actually clicked.


