URL shortening service using AWS Lambda Functions as compute and Redis Cloud as a backend database.
Before taking a position as a Software Engineer @ Redis, I wanted to practice system's design.
That's why I designed a cloud URL shortening service MVP in 3 weeks with 2 lambdas, a free Redis Cloud database and a barebones frontend.
The project has is evolving into a complex system. I am continuously developing this project to improve my skills.
For the original design narrative (requirements, capacity estimates, API, Redis key schema, diagram, and deep dives), see docs/system-design.md.
For formal requirements, high-level architecture, ADRs, and how to read the docs, start at docs/README.md.
This project uses SAM and Docker to run AWS Lambda Functions in containers. A docker compose stack provides Redis, Redis Insight, Localstack (parameters and secrets locally), and the AWS AppConfig Agent.
NOTE: Localstack is used for hybrid deployment (having AWS AppConfig in cloud, AWS lambda in local containers).
All commands assume you run from the repository root. Use the root Makefile (make help for targets).
- Python 3.13+
- uv (Python package manager)
- Node.js (for frontend)
- Docker v 29.1+ and Docker Compose v2.40+
- SAM
- Install dependencies:
make install - Start Docker Compose:
make up - Build backend, frontend, and infra:
make build - Start the local SAM API (backend) and Vite dev server (frontend):
make dev
Then:
Sample events live under events/.
Invoke ShortenUrlFunction:
make invoke FUNCTION=ShortenUrlFunction EVENT_FILE=events/shorten_url/event.jsonInvoke RedirectUrlFunction:
make invoke FUNCTION=RedirectUrlFunction EVENT_FILE=events/redirect_url/event.jsonWhat happens if you get some error related to unsupported arm64 architecture?
In infra/stacks/backend/template.yaml you might notice that the Lambda runtimes are
in arm64 containers. It's because I'm developing on a Mac which is why native
x86_64 didn't work for me well.
If you are developing on x86_64 architecture (e.g. a Linux distribution), you
can switch out the architecture inside infra/stacks/backend/template.yaml:
Globals:
Function:
Timeout: 30
Tracing: Active
LoggingConfig:
LogFormat: JSON
Runtime: python3.13
Architectures:
- x86_64 # <-- switch this from `arm64` to `x86_64`AWS CloudFormation manages cloud resources.
All commands assume you run from the repository root. Use the root
Makefile (make help for targets).
- AWS Free Tier account (paid one also works)
- uv (Python package manager)
- act and/or GitHub with GitHub Actions
The root Makefile reads variables from the environment (and from the command line). To avoid exporting values in every shell session, keep non-sensitive settings in .vars and secrets in .secrets at the repo root. Both filenames are gitignored.
Use POSIX-style export lines so you can load them with the shell source builtin:
.vars (optional; defaults match the Makefile if omitted):
| Variable | Default | Description |
|---|---|---|
APP_NAME |
cloudshortener |
Application / stack name prefix |
APP_ENV |
dev |
Environment (dev, staging, prod, …) |
LOG_LEVEL |
INFO |
Lambda log level |
AWS_REGION |
eu-central-1 |
AWS region |
AWS_PROFILE |
personal-dev |
AWS CLI profile |
GENERATE_FRONTEND_CONFIG |
false |
If true, post-deploy generates frontend/config/<APP_ENV>/app.config.json from stack outputs |
REDIS_PORT |
6379 |
Local Compose: Redis port |
REDISINSIGHT_PORT |
5540 |
Local Compose: Redis Insight port |
LOCALSTACK_EDGE_PORT |
4566 |
Local Compose: Localstack edge port |
LOCALSTACK_AUX_PORT |
4571 |
Local Compose: Localstack auxiliary port |
APPCONFIG_AGENT_PORT |
2772 |
Local Compose: AppConfig Agent port |
.secrets (required for make deploy):
| Variable | Description |
|---|---|
ELASTICACHE_PASSWORD |
32–128 printable ASCII characters, no spaces, no /*, ", @, with at least one uppercase letter and one digit. Example: bP7f2Qk9LxN4Rz8TgH3mVw6YcJ5pK1sD. |
Example .vars:
export APP_NAME=cloudshortener
export APP_ENV=dev
export AWS_REGION=eu-central-1
export AWS_PROFILE=personal-dev
export GENERATE_FRONTEND_CONFIG=falseExample .secrets:
export ELASTICACHE_PASSWORD='your-password-here'Before running make targets that need these values, load the files into your shell (omit either file if you do not use it):
set -a
[ -f .vars ] && . ./.vars
[ -f .secrets ] && . ./.secrets
set +aYou can also pass any variable on the command line instead, e.g. make deploy ELASTICACHE_PASSWORD='...' APP_ENV=staging.
- Deploy OIDC stack (allows GitHub Actions and
actto deploy to AWS):
make bootstrapTo use an existing OIDC provider:
make bootstrap EXISTING_OIDC_PROVIDER_ARN=arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.comOverride GITHUB_ORG and REPO_NAME if needed (see make -C infra/bootstrap help).
- Create dev environment configuration files by editing and renaming the files:
- config/shorten_url/dev.example.yaml →
config/shorten_url/dev.yaml - config/redirect_url/dev.example.yaml →
config/redirect_url/dev.yaml
cp config/shorten_url/dev.example.yaml config/shorten_url/dev.yaml
cp config/redirect_url/dev.example.yaml config/redirect_url/dev.yaml
# Edit both files with your config valuesNOTE: dev.yaml / staging.yaml / prod.yaml are in .gitignore, so your secrets won't be committed.
- Run seeding scripts, deploy SAM stack, and upload frontend:
make deploy GENERATE_FRONTEND_CONFIG=trueThe FrontendUrl is printed in the deploy output and is also available on the CloudFormation stack’s Outputs tab in the AWS Console.
make destroyHappy shortening!
This project is distributed under the MIT license.