Blog 3

LESSONS I LEARNED WHILE DESIGNING A SERVERLESS QR ATTENDANCE SYSTEM ON AWS

INTRODUCTION

Before starting to build the QR attendance system, I had never developed a Serverless application. Most of my previous knowledge revolved around traditional web models, where a single server handled all business logic and connected directly to the database. Therefore, choosing AWS Serverless for this workshop was both an opportunity to learn new technologies and a challenge to shift my system design mindset.

After researching official AWS documentation and gradually building the architecture, I realized that designing a Serverless system isn’t just about piecing AWS services together. It’s more about understanding the role of each component and how they coordinate to create a scalable, maintainable, and easily deployable system.

1. DON’T BUNDLE TOO MANY TASKS INTO A SINGLE LAMBDA

Initially, I planned to build a single Lambda to handle all system functions like creating attendance sessions, generating QR codes, receiving check-ins, and exporting reports. This approach was very similar to building a traditional Web API.

However, after reading AWS Lambda documentation, I realized each Lambda should perform “one specific task.” Thus, I separated the system into multiple Lambdas: Session, QR Generator, Check-in, Report, and Admin.

This division makes the source code easier to maintain, and each Lambda can be deployed or scaled independently. If the check-in function experiences higher traffic later on, AWS can scale the Check-in Lambda without affecting the entire system.

2. API GATEWAY IS NOT JUST FOR DEFINING APIS

When I first learned, I thought API Gateway only forwarded HTTP requests to Lambda.

After practical implementation, I realized API Gateway also handles many critical functions such as verifying JWT Tokens from Amazon Cognito, routing requests to the correct Lambda, handling CORS, and logging access.

As a result, the code inside Lambda can strictly focus on business logic. This clarifies the system and significantly reduces the amount of common handling code repeated in multiple places.

3. AMAZON COGNITO SIGNIFICANTLY REDUCES THE SECURITY BURDEN

One of my most important decisions was using Amazon Cognito instead of building a custom login system.

Initially, I thought storing accounts in DynamoDB and manually checking passwords with Lambda would be simpler. However, after reading AWS documentation, I realized user authentication is a complex domain involving password encryption, session management, and issuing JWT Tokens.

By letting Cognito handle the entire authentication process, Lambda completely avoids processing password information. This makes the system more secure and drastically cuts down the code I had to write myself.

4. DYNAMODB DESIGN SHOULD START WITH ACCESS PATTERNS

Initially, I designed DynamoDB like a relational database, focusing only on storing data. However, in AWS documentation, DynamoDB is recommended to be designed based on “Access Patterns,” meaning how the data will be queried.

For example, the system needs to fetch a list of attendance sessions by instructor. If using a Scan on the entire Sessions table, performance degrades as data grows. Therefore, I added a “Global Secondary Index (GSI)” based on the creator’s ID (e.g., “accountId”) to use Query instead of Scan.

Through this example, I realized that with DynamoDB, how data is queried must be defined right from the design phase, not after the database is built.

5. SECRETS MANAGER SEPARATES SENSITIVE INFORMATION FROM SOURCE CODE

In the QR attendance system, I use a secret key to generate and validate QR codes.

Initially, I planned to store this key directly in the source code as a configuration variable. However, this is unsafe because the secret key could accidentally be pushed to source control.

After learning about AWS Secrets Manager, I moved all sensitive information to this service. Lambda only reads the key when needed via IAM access permissions.

This makes the source code safer and more convenient when the key needs changing without modifying the program.

6. AWS SAM MAKES THE ARCHITECTURE MUCH CLEARER

Before using AWS SAM, I thought “template.yaml” was just a configuration file for deployment.

During development, I realized this is actually a complete description of the system. Just by opening “template.yaml”, I can see how many Lambdas the system uses, what DynamoDB tables exist, how API Gateway is configured, and what IAM permissions are granted.

This makes project management much easier compared to manual configuration on the AWS Management Console.

7. SERVERLESS ARCHITECTURE DOES MORE THAN JUST REDUCE ADMINISTRATIVE OVERHEAD

Previously, I thought the biggest benefit of Serverless was not needing to manage servers.

After completing the system design, I realized a greater value lies in each AWS service being built for a very clear role. Lambda handles business logic, API Gateway receives requests, Cognito authenticates users, DynamoDB stores data, and Secrets Manager protects sensitive information.

This separation gives the system a clearer structure, and each component can be developed or replaced with minimal impact on others.

CONCLUSION

Looking back at the entire project, what I learned was not just how to use AWS services, but a new way of thinking about system design.

Instead of building an application around a central server, Serverless architecture encourages breaking the system down into independent components, each handling a specific task and fully managed by AWS infrastructure. This allows developers to focus more on solving business problems rather than spending time on system administration.

In my opinion, the project’s greatest value wasn’t in how many AWS services were used, but in the step-by-step process of understanding why each service was chosen and how they coordinate to form a complete architecture. This is also the most important lesson I took away after designing the QR attendance system using AWS Serverless.

Original post on AWS Study Group: Original Post