The learning curve between writing code for a class and engineering a production-ready application is wider than most students realize. In ICS 414: Software Engineering II, I bridged this opening by developing FreshKeep, a web application designed to track grocery inventories and maximize food consumption. Throughout the semester, I practiced structured and collaborative work expected in professional settings. By moving through modules on Agile methodologies, automated testing, and CI/CD, I progressed into a disciplined software engineer. I learned that high-quality software is a result of consistent, repeatable practices rather than individual bursts of effort.
A user story is a short, informal description of a software feature written from the perspective of an end user to define the “who, what, and why” of a requirement while serving as a placeholder for ongoing conversation and refinement. Switching to a workflow where GitHub Discussions serves as our primary user-story hub has been a game-changer, effectively linked between raw requirements elicitation and actionable development. By focusing our process on the “Three Cs” logic - Card, Conversation, and Confirmation - we structure every entry using the standard “As a / I want / So that” template to confirm that user value always takes precedence over technical implementation. The real magic was supposed to happen during the collaborative refinement within the discussion threads, where we could flesh out acceptance criteria and split broad epics into sprint-ready slices, but we went straight to promoting them to GitHub Issues. This transition from high-level dialogue to granular tracking on project boards creates a transparent, end-to-end pipeline that ensures nothing is lost in translation, making the jump from a conceptual user story to a completed feature highly efficient.

Screenshot 1: User stories at GitHub Discussions
A primary takeaway from the IDPM Project Management (IDPM) was the shift toward Agile process models, team coordination, planning, and issue management. For FreshKeep, we utilized GitHub Projects and Issues to synchronize our efforts. We assigned each issue to a specific developer, linked it to a milestone, and tracked its progress on a GitHub Project board. This approach removed the haziness of who was doing what. I realized that a well-defined issue - complete with a description of the goal and acceptance criteria - is the best way to prevent miscommunication.

Screenshot 2: A snapshot of our GitHub Project board showing the transition from “In Progress” to “Review” in one of many milestones. Clear labeling ensured no two developers worked on the same logic simultaneously.
During our third sprint, two team members (myself included) worked on features that touched the same inventory logic. When we both submitted pull requests, we encountered a significant merge conflict that threatened to stall development. We scheduled a quick call, shared screens, and walked through the conflicting changes line by line. By discussing our intentions and referencing the acceptance criteria in the related issue, we decided which changes to keep and which to refactor. Resolving this conflict unblocked our workflow and improved team communication via mutual understanding of the codebase.
Selecting a technical stack is a strategic decision that impacts long-term maintainability. For FreshKeep, we choose Next.js for its robust framework capabilities, TypeScript for rigorous type safety, and Prisma ORM to abstracts our database interactions. This combination provided a “type-safe” bridge from the database to the UI, significantly reducing runtime errors. By prioritizing a stable and reproducible development environment, we shifted our focus away from troubleshooting toolchains and toward delivering user-facing functionality.
For most students, testing is an afterthought - a final step completed only if time allows. The Testing module reframed testing as a core part of the development cycle. We integrated Playwright for end-to-end (E2E) testing to simulate real user interactions on FreshKeep. We wrote tests to ensure that a user could sign up, add an item, and see it appear in their dashboard with the correct expiration. Seeing a “green checkmark” on a GitHub Action after pushing code provided a level of confidence. It allowed me to refactor complex parts of the inventory logic without fearing that I was silently breaking the login flow.
// Example of a Playwright test ensuring the inventory adds items correctly
test('should allow a user to add a new inventory item', async ({ page }) => {
await page.goto('/inventory');
await page.click('button#add-item');
await page.fill('input[name="itemName"]', 'Organic Milk');
await page.click('button#submit');
await expect(page.locator('text=Organic Milk')).toBeVisible();
});
The IDPM Review module introduced the rigors of the Pull Request (PR) process. No code entered our main branch without a review from a peer. This was often the most educational part of the week. Reviewing a teammate’s PR forced me to test code and think about edge cases I might have missed in my own work. Likewise, receiving feedback on my PRs helped me clean up my styling and simplify my API calls. We learned to keep our PRs small - usually under 200 lines - to make reviews efficient. This habit of constant peer feedback ensured that the final FreshKeep application accurately revealed an organized system, rather than separate coding styles mashed together. In addition, we included Copilot reviews in all our PRs, which sometimes helped improve our code quality. For example, Copilot often caught minor bugs or suggested more efficient solutions that we might have missed.

Screenshot 3: Deployed full-stack web application.
Taking a project from a local machine to a live URL (freshkeepuh.live) presented a new set of challenges. We had to manage environment variables, secure our database connections, and ensure that our build process was optimized for production. We chose Heroku for hosting and have never regretted it. The CLI tools let us quickly deploy our Next.JS app after the first demo at the start of class, so our site stayed live throughout development.
This stage of the project taught me about the “DevOps” side of software engineering. I learned that an application is only as good as its availability. Setting up a Continuous Deployment pipeline meant that as soon as our tests passed on the main branch, the changes went live for our users. This automation allowed us to iterate quickly based on user feedback during the weeks of the semester.
Looking back at the course modules, the most significant change I experienced was in my mindset. I stopped seeing software as just a collection of files and started seeing it as a lifecycle. FreshKeep is a functional tool that solves a real problem, but it is also a proof of my ability to work within a professional system. I now understand that the time spent writing documentation, setting up CI/CD pipelines, and conducting peer reviews is not “extra” work - it is the work. I am leaving you with a clear understanding of how to build reliable, scalable software that can survive in a production environment.