How to Build a Serverless Portfolio Project: A Complete Guide
A serverless portfolio project is one of the fastest ways to demonstrate cloud engineering skills without needing a complex multi-VM setup. But most beginner serverless projects look identical: a Lambda function returning “Hello World” with admin IAM permissions and no tests. This guide walks through how to build one that actually stands out.
What to build: a URL shortener API
Rather than leaving the project choice open-ended, this guide uses a concrete example: a URL shortener. It is simple enough to build in a day, complex enough to demonstrate real architecture decisions, and recognisable enough that any interviewer can understand it instantly.
The architecture:
- An API Gateway (AWS) or Cloud Endpoints (GCP) receiving HTTP requests
- A Lambda function (AWS) or Cloud Function (GCP) handling create and redirect logic
- A DynamoDB table (AWS) or Firestore collection (GCP) storing URL mappings
- A custom domain with HTTPS (optional but impressive)
Two operations:
- POST /shorten — accepts a long URL, stores a short code, returns the short URL
- GET /{shortCode} — looks up the short code and returns a 301 redirect to the original URL
You are not trying to build Bitly. You are trying to demonstrate that you can design a working serverless system with correct IAM, proper error handling, and documented decisions.
IAM design: the most important part
The IAM configuration is where most beginner serverless projects fail. A Lambda function with AdministratorAccess attached to its execution role is a red flag. It signals that the developer did not think about security — or thought about it and chose convenience instead.
The correct approach for this project:
- Create a dedicated IAM execution role for the Lambda function
- Allow only
dynamodb:GetItemanddynamodb:PutItemon the specific DynamoDB table ARN — not all DynamoDB tables - Allow
logs:CreateLogGroup,logs:CreateLogStream, andlogs:PutLogEvents— the minimum needed for CloudWatch logging - Nothing else
The policy should look something like:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:eu-west-1:123456789:table/UrlShortener"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}Explain this in your README. It is a small thing that signals a large amount of security awareness.
Provision everything with infrastructure as code
Do not build this by clicking through the AWS or GCP console. The project should be 100% reproducible from the repository. Use Terraform, AWS SAM, the Serverless Framework, or GCP Deployment Manager.
Terraform is the most portable and the most commonly expected in job listings. If you are not confident with Terraform yet, AWS SAM or the Serverless Framework are simpler alternatives that are still better than console-based deployment.
The infrastructure code should define:
- The API Gateway or HTTP trigger
- The Lambda function and its runtime, handler, and memory settings
- The IAM execution role and policy (as above)
- The DynamoDB table with partition key defined
- CloudWatch log group with a sensible retention period (90 days is reasonable — “never expire” is not)
Error handling and edge cases
A project that only handles the happy path looks unfinished. Consider and handle:
- What happens if someone submits an invalid URL? Return a 400 with a meaningful error message.
- What happens if the short code in the GET request does not exist? Return a 404, not a 500.
- What happens if DynamoDB is unavailable? The Lambda will timeout — document this behaviour and how you would improve it in a production system.
You do not need to handle every possible failure mode. You do need to show that you thought about failure modes.
Secrets and configuration
This project does not have many secrets — the Lambda authenticates to DynamoDB via its IAM role, not a password. But if you add a feature that requires an API key (for example, rate limiting via an external service), store that key in AWS Secrets Manager or GCP Secret Manager and retrieve it at runtime. Never hardcode it in the function code or in environment variables visible in the console.
Even without secrets, document that you are using IAM roles for authentication rather than credentials. This is the correct serverless authentication pattern and worth calling out explicitly.
Testing
Include at least unit tests for the core logic — the short code generation, URL validation, and response formatting. Serverless functions are easy to test in isolation because they are just functions that take an event object and return a response.
Add a CI pipeline using GitHub Actions that runs the tests on every pull request. If the tests pass, the pipeline can package and deploy to a test environment. This makes the project a CI/CD project as well as a serverless one — increasing its portfolio value.
What to document in the README
The README should cover:
- Architecture overview — a brief description of the components and how they connect. A simple diagram is a bonus.
- IAM design — what permissions the function has and why those specific permissions.
- Deployment instructions — how to deploy this to a new AWS account from scratch. If someone cannot deploy your project from the README, it is incomplete.
- What you would change in production — throttling limits on the API Gateway, DynamoDB auto-scaling, caching for popular short codes. This section shows you understand the gap between portfolio projects and production systems.
For more on what makes a strong README and how to write about your projects for job applications, see how to write a cloud portfolio case study.
Variations if you want to go further
If the URL shortener feels too simple for your level, extend it:
- Add authentication so only registered users can create short URLs (Cognito or Firebase Auth)
- Add a click counter stored in DynamoDB and a stats endpoint
- Add a TTL on the DynamoDB item so short URLs expire automatically (DynamoDB has native TTL support)
- Add rate limiting via API Gateway usage plans or a Lambda authoriser
Each extension adds a decision point to document and another skill area to demonstrate. Stop when the project covers what you need — not when you run out of ideas.
Summary
- Use a URL shortener as the project — simple enough to build fast, complex enough to demonstrate real decisions
- IAM is the most important part: the Lambda execution role should allow only the minimum permissions on the specific resource
- Provision everything with infrastructure as code — the project should be deployable from the repository without console clicks
- Handle at least three edge cases: invalid input, missing resource, and one service failure scenario
- Include a CI pipeline that runs tests — this makes the project a CI/CD showcase as well
- Document what you would do differently in production — this is what separates a thoughtful engineer from someone who just followed a tutorial