Building My First End-to-End AWS App with 7 Services ( AWS Amplify , Cognito , API Gateway etc)


Introduction
In this blog, I’ll walk you through how I built the entire project from scratch, explain the AWS services used and how they work together, share the issues I ran into and how I fixed them, and reflect on everything I learned about CI/CD, serverless, permissions, and the magic of infrastructure that just works.
This is more than just a tutorial. It’s my experience and my honest thoughts on learning AWS by building something practical and fun.
Whether you’re just getting started with cloud or looking for a solid hands-on project to sharpen your skills, you’ll hopefully find this useful, beginner-friendly, and maybe even a little inspiring.
What We’re Building – The Wild Ryde App
The project is called WildRydes, and at first glance, it might sound silly — a ride-sharing app where users can request unicorns to pick them up. But don’t let the playful theme fool you. Under the hood, this app is a solid example of how to build a modern cloud-native application using real AWS services and production-ready architecture.
The final result is a fully deployed web application with user registration and login, a public URL hosted on AWS Amplify, a dynamic frontend that interacts with the backend using API Gateway and Lambda, and persistent ride data stored in DynamoDB. You even get to see CI/CD in action, where any changes pushed to the GitHub repo automatically trigger a new deployment.
From clicking on a map to request a unicorn, to authenticating users with Cognito and recording ride history in a database, everything feels cohesive. And the best part? It’s not just a toy project. You end up with a live, working app that you can show off to friends, recruiters, or anyone who appreciates a good serverless setup.
I wasn’t just following steps blindly either. Each feature made me think about how cloud services interact what triggers what, where to plug in configs, how roles and permissions work, and how to debug when things go wrong.
Services Used and Why They Matter
What made this project especially valuable for me was how many core AWS services it brought together in one practical flow. Instead of just learning these services in isolation, I got to see how they interact and rely on each other to make the app work.
Here’s a quick overview of the services I used and what role each one played in the app:
GitHub
This is where all the source code lives. It's also connected to AWS Amplify to enable automatic deployments every time I push changes. Using GitHub templates made it super easy to clone the original project and start working in my own repo.
AWS Amplify
Amplify handles the frontend hosting and CI/CD. I connected it to my GitHub repo, and every time I pushed a commit, Amplify picked it up and deployed the latest version of the app. No need to manually upload files or manage EC2 instances . it just worked.
Amazon Cognito
This is the user authentication service. It lets users register, log in, and verify their email addresses. I manually created a user pool and added it to the app’s config so that it could securely manage user sessions and issue JWT tokens for access control.
AWS Lambda
Lambda functions are the heart of the backend logic. When a user clicks to request a unicorn ride, it triggers a Lambda function. The function handles selecting a unicorn, generating a ride ID, and saving the data to DynamoDB.
Amazon DynamoDB
This is the NoSQL database where all ride details are stored. Every time a ride is requested, the Lambda function writes the ride information here — including the user, location, and unicorn details.
Amazon API Gateway
API Gateway connects the frontend with the backend. It exposes a REST API endpoint that the frontend can call when a user requests a ride. The API is secured using Cognito, so only logged-in users can access it.
AWS IAM (Identity and Access Management)
IAM controls permissions between all the services. For example, my Lambda function needed permissions to write to DynamoDB, and API Gateway had to be allowed to invoke the Lambda. Setting up these roles correctly was critical to making everything work.
By the end of the project, I had a much clearer understanding of what each of these services does, how they connect, and how to debug when they don’t behave as expected.
Step-by-Step Build Walkthrough
1. Setting Up the GitHub Repository
The first step was to get the source code for the app into my own GitHub account. The tutorial creator had already uploaded the full code to a public template repo, which made this part super smooth.
I used the “Use this template” button to create a new repository in my own GitHub account. That instantly copied all the code frontend, backend, and Lambda logic into a fresh repo where I could make my own edits.
Beginner tip: If your repo doesn’t show up inside AWS Amplify later, make sure to give Amplify the right GitHub permissions. I had to refresh and reconnect it a couple of times before it picked up my new repo.
Once the repo was set up, I was ready to deploy the frontend.
2. Deploying the Frontend with AWS Amplify
With the code sitting in my GitHub repo, it was time to get the frontend live. For that, I used AWS Amplify, which honestly felt like magic once it was properly set up.
I opened the Amplify service in the AWS console, clicked “New app,” and chose GitHub as the source. After authorizing Amplify to access my GitHub account, I selected the repo I had just created and followed the prompts to deploy it.
Amplify automatically detected the framework and build settings, then kicked off the first deployment. In a few minutes, I had a working site live on a public URL.
What really blew my mind was the CI/CD part. I made a small change to the index.html
file directly in GitHub — just tweaking a line of text — and Amplify instantly triggered a new build and redeployed the site. The update was live in less than a minute.
Beginner tip: If your repo doesn’t show up during the Amplify setup, try refreshing your GitHub token or manually adjusting the permissions. I had to try twice before mine appeared.
What I learned here:
Amplify isn’t just a hosting platform. It’s also your CI/CD pipeline, your frontend deployment engine, and your fast feedback loop. For small to medium apps, it removes a huge chunk of deployment headaches.
3. Adding Authentication with Amazon Cognito
Now that my frontend was live, I needed a way for users to sign up and log in. That’s where Amazon Cognito comes in.
Cognito handles authentication for the app letting users register with their email, verify it, and log in securely. The original app was built before Amplify Gen 2, so instead of defining auth in code, I set up the user pool manually from the AWS Console.
I created a new user pool, enabled username-based sign-in, and chose to let Cognito handle the email confirmation step. I named my pool something like wild-rides-2024
to avoid conflicts with older versions I had tested.
Once the user pool was ready, I copied two key things:
The User Pool ID
The App Client ID
I then went back to my GitHub repo, opened the config.js
file, and pasted these values into the relevant fields along with the correct AWS region (us-west-2
in my case). After committing those changes, Amplify picked them up, rebuilt the app, and deployed a new version with authentication wired in.
I tested the signup flow right away. I used a real email so Cognito could send me a verification code. After signing up and logging in, I landed on a page with a long JWT token, which I copied and saved. That token would later be used to test my API.
Beginner tip: Don’t skip saving that token you’ll need it when testing your API Gateway setup later. Also, double-check that your region in the config file matches the one where you created the user pool. That’s an easy thing to mess up.
What I learned here:
Even though Cognito can feel a little clunky at first, it handles a lot like registration, login, email verification, token management all without writing a single line of backend auth logic.
4. Creating the Backend Logic with AWS Lambda
With authentication working, it was time to build the backend logic that powers the actual “request a ride” feature. In this project, all of that happens inside a Lambda function a bit of code that runs whenever a user clicks on the map to request a unicorn.
The Lambda function is responsible for:
Selecting a unicorn from the fleet
Generating a ride ID
Capturing the logged-in user’s name from Cognito
Grabbing the pickup location from the frontend
Writing all of that to DynamoDB
Before I created the function, I had to set up two things:
A DynamoDB table named
rides
(orrides2024
in my case). I set the partition key asrideId
and copied the ARN of the table to use in permissions.An IAM role that gives Lambda permission to write to DynamoDB. I created a new role for Lambda, attached the basic execution policy, and added a custom inline policy with
PutItem
access — scoped only to myrides
table for better security.
Once those were ready, I headed over to the Lambda section in AWS, created a new function called requestUnicorn
, selected Node.js 20 as the runtime, and attached the IAM role I had just created.
I then replaced the boilerplate code with the actual function code from the GitHub repo. This function randomly picks a unicorn, builds the ride data, and writes it into the DynamoDB table.
Since I had named my table rides2024
, I made sure to update the table name in the Lambda code too. That was an easy spot to break things if I forgot.
To test it, I created a mock event with a sample payload and ran the test inside the Lambda console. It returned a 201
status with a unicorn name and ride ID, which meant it worked!
Then I checked my DynamoDB table, and sure enough there was a new ride entry.
Beginner tip: Double-check your table name in the Lambda code. Also, don’t forget to attach the right permissions. Lambda won’t complain it’ll just silently fail if it doesn’t have access to write to DynamoDB.
5. Connecting Everything with API Gateway
With the Lambda function ready and working, the next step was to make it accessible from the frontend. That’s where API Gateway came in. It acts as the bridge between the client and the backend, exposing an HTTP endpoint that the frontend can call to trigger the Lambda function.
I opened API Gateway in the AWS console and created a new REST API called wild-rides
(mine was wild-rides-2024
). Then, I set up a new resource with the path /ride
and created a POST method under it. The integration type was set to Lambda proxy, and I connected it to the requestUnicorn
Lambda function I had created earlier.
Since my app uses Cognito authentication, I had to secure this endpoint. That meant creating a new Cognito Authorizer inside API Gateway. I selected the user pool I had previously created, entered Authorization
as the token source, and linked it to the /ride
endpoint under the method request settings.
To confirm everything was working, I went back to the authorizer, pasted in the JWT token I got after logging in earlier, and hit “Test.” It returned a 200 response with user info — a good sign.
I deployed the API to a new stage named dev
, and copied the Invoke URL. Then I opened up my config.js
file in GitHub again, pasted the URL into the appropriate field, and committed the change. Amplify picked it up and redeployed the updated frontend.
Now, the frontend could make authenticated POST requests to API Gateway, which would in turn invoke the Lambda function and process a ride request.
Beginner tip: Don't forget to enable CORS when creating the resource or the method. Without it, your frontend won’t be allowed to talk to the API, and you’ll get vague browser errors that are tough to debug.
Lessons I Learned
1. IAM roles are the glue that holds everything together
Every AWS service has its own security boundaries, so nothing talks to anything else without explicit permission. I couldn’t just “guess and hope.” Reading error messages, attaching precise policies, and limiting access to exactly what the function or API needed saved me from future headaches.
2. CI/CD feels magical once it clicks
Watching Amplify pick up a commit, build the app, and push the latest version live in minutes was a game‑changer. It turned deployment from a chore into part of my normal development loop. Small, incremental commits let me catch mistakes early instead of after a big, scary release.
3. Serverless equals responsibility, not invisibility
Lambda and DynamoDB hide the servers, but they don’t hide bad configurations or sloppy code. Cold starts, IAM misconfigurations, and unhandled errors still matter. “Set and forget” works only if you actually set things up correctly.
4. Meaningful names and quick notes save hours
I kept a scratch file with every ARN, URL, and token I touched. Copy‑and‑paste errors disappeared, and switching between AWS regions or accounts became far less confusing.
5. CloudWatch logs are the first place to look when things break
Instead of guessing why a Lambda was failing, I opened the log stream and read the stack trace. Nine times out of ten, the answer was right there waiting.
6. A playful project keeps motivation high
Requesting unicorn rides is silly, but that fun factor pushed me to finish the build instead of abandoning it halfway. A whimsical theme can turn a daunting learning curve into something you actually look forward to.
These takeaways now guide every new cloud project I tackle, reminding me that the best learning happens by doing especially when the doing involves a fleet of imaginary unicorns.
RESULT:
Cleanup: Don’t Forget to Delete Resources
One of the most important parts of any AWS project especially if you're learning or experimenting is cleaning up properly. These services might be free under the AWS Free Tier, but if you forget to shut them down, they can slowly rack up costs.
Here’s what I deleted after testing was complete:
1. Amplify App
I went to the Amplify console, selected the app, opened the settings, and hit “Delete App.” That shuts down the hosting and CI/CD pipeline.
2. Cognito User Pool
Deleted the user pool from the Cognito console. If left running, it doesn’t cost much, but it’s best to clean up unused identity resources.
3. Lambda Function
Removed the requestUnicorn
function from the Lambda console. Even if it’s serverless, Lambda can still generate CloudWatch logs that persist over time.
4. IAM Role
Deleted the custom Lambda execution role to keep things tidy and avoid permission clutter.
5. DynamoDB Table
Dropped the rides
(or rides2024
) table. I unchecked backups and confirmed deletion.
6. API Gateway Endpoint
Deleted the REST API that exposed the /ride
endpoint. Don’t forget to remove the deployment stage as well.
7. CloudWatch Logs
Manually removed the log group related to the Lambda function to free up log storage.
8. GitHub Repository (Optional)
If you don’t plan on using the code again, you can delete the GitHub repo or just mark it as archived. I kept mine around for future experiments.
Beginner tip: Make a checklist. AWS doesn’t have a single “delete project” button, so it’s easy to forget things.
What I learned here:
Building is fun, but managing your cloud responsibly is part of the real-world developer mindset. Knowing how to clean up is just as important as knowing how to launch.
Final Thoughts
This project was the most fun I’ve had learning AWS so far.
It wasn’t just another copy-paste tutorial, it was hands-on, practical, and full of little challenges that forced me to actually understand what I was doing. By the end of it, I had a working cloud app powered by seven real AWS services, a deeper understanding of serverless architecture, and a growing confidence that I could build (and break and fix) things on my own.
If you're someone who's learning AWS and wants to do more than follow static examples, I highly recommend giving this project a shot. This project is based on the video by Tiny Technical Tutorials..
Thanks to the TTT for such a well-structured walkthrough.
And if you made it to the end of this blog, thank you for reading.
Feel free to connect with me or check out more of my cloud learning journey over on Hashnode and LinkedIn. And if you try the project yourself , let me know. I’d love to see
Subscribe to my newsletter
Read articles from Kirti directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Kirti
Kirti
Hi, I'm KirtiI’m actively learning AWS, Python automation, and cloud best practices through real-world projects. Every challenge is a step forward, and every solution is something worth sharing. On this blog, you’ll find: Simplified guides on AWS core services Lessons from my journey breaking into cloud engineering I believe in learning in public—and this blog is where I document my progress, challenges, and wins.