Inspiration
As SMU students, we often struggle to get food during short breaks between classes, especially with back-to-back classes or when classrooms are far from nearby food outlets. Traditional food delivery apps are often too expensive, and riders cannot enter campus buildings, let alone deliver to specific seminar rooms.
smunch was inspired by this daily pain point: we wanted to build a platform that makes it incredibly easy and affordable for students to get food delivered straight to their classes.
What it does
smunch is a web application that allows SMU students to order food directly to their classroom (or anywhere in the school). Students can choose a delivery date, time, and seminar room location (e.g., SCIS SR 2-2), browse from a list of supported merchants, add items to their cart, and checkout with a fixed low delivery fee. After payment, the order is confirmed and a runner delivers it to their selected seminar room. The app also provides real-time status updates and email confirmations. SMU students like cute stuff. We motivate students to use the app through mini games and the ability to collect different smunchys.
How we built it
We built smunch using Vue.js 3 for our frontend, Vite as the build tool, and Vue Router for client-side navigation, while using Pinia for state management and Axios for API communication. The backend was built with Node.js and Express.js as the web framework, with Supabase serving as the primary database for user management, orders, menus, and merchant data, Redis for caching and session management, Firebase and ImageKit for real-time features and file storage, and Cloudflare Turnstile for bot protection.
The app features JWT-based authentication with role-based access control, real-time order tracking via WebSocket connections, payment processing integration, a parent-child merchant hierarchy for restaurant chains like Koufu, AI-powered chat interface for food recommendations, gamification with a coin system and smunchy collection, live weather integration to assist users with order decision making, and automated email notifications via cron jobs, all following a modern software architecture with clear separation between frontend and backend to provide a smooth user experience for campus food ordering and delivery tracking.
Challenges we ran into
1) Designing a clean user flow for delivery scheduling, merchant selection, and checkout within a few simple steps.
2) Integrating a flexible yet secure payment verification method, especially without direct integration to PayNow APIs.
Accomplishments that we're proud of
1) Creating an end-to-end working prototype that allows students to place real food orders to specific seminar rooms.
2) Building a user-friendly interface from scratch with minimal friction during the ordering process.
3) Designing a practical delivery model that works within the unique constraints of a university campus.
4) Receiving positive feedback from testers who appreciated the convenience and simplicity of the platform.
5) Gamifying the proces with coins and smunchy collection to enhance the user experience and introduce an element of fun and wonder in our userbase.
What we learned
1) We learned that tightly defining and prioritizing our Minimum Viable Product (MVP) was key to getting a functional app up and running within our timeline. By focusing on core use cases such as single-user ordering, fixed delivery time slots, we were able to build, test, and refine our system without getting overwhelmed by complexity. This taught us the value of starting small, validating quickly, and iterating based on user needs.
2) Working as a team taught us how to divide frontend and backend tasks clearly, and how to sync progress using GitHub branches, pull requests, and project boards. We learned to document our decisions, write modular code, and resolve merge conflicts efficiently. Regular communication and setting shared coding conventions helped us avoid blockers and kept development productive.
3) Focus on being user-centric. Have empathy Throughout the process, we constantly reminded ourselves that the goal was to make food ordering easy (and afforable) for real students like us. This meant simplifying interfaces, minimizing clicks, and building a system that worked well even under time pressure (e.g., just before class). We learned to have empathy and to design not for ourselves as developers, but for our peers who might be rushing, hungry, or stressed. For us, usability > feature count.
4) Several early features and assumptions didn’t work as we imagined. This included ideas around real-time payment checking or automated merchant coordination. But we learned that quick testing and iteration helped us move past these challenges instead of being stuck trying to perfect something from day one.
Balancing simplicity with flexibility in database and API design Designing the schema taught us how to balance normalization and real-world usage — such as storing flexible delivery locations (e.g., SCIS SR 2-2) while ensuring data integrity. On the API side, we practiced keeping endpoints RESTful, clean, and scalable for future additions like more buildings or time slots.
What's next for smunch
Eventually, we aim to support runner-side dashboards and shift from manual runner allocation to real-time job matching. smunch has the potential to become the go-to food delivery system within SMU — convenient, reliable, and made by students, for students.
We plan to expand our user base and ensure that our pipelines and infrastructure is able to handle such load. We will conduct load testing using Loadium or similar tools.
Furthermore, we intend to make the in-app mini games and collection/reward system even more enticing and educational - where we will allow users to unlock more smunchys, and unlocking a smunchy will help the user discover a 'hidden food gem' in Singapore. We can use this opportunity to introduce people to different food gems across Singapore and foreign students in SMU can use this as one way to get to know our country better.
Frontend Key Features
1) “Play” in Navbar The "Play" in the nav bar enhances user engagement by offering a fun gaming experience. Beyond entertainment, users are seamlessly redirected to the corresponding merchant’s order page after playing a game. This strategic flow encourages users to explore merchant menus, ultimately increasing the demand for food and drink purchases on Smunch.
2) Coin Feature a) Each user has a coin balance associated with their account. The number of coins is displayed at the top right corner of the nav bar. b) Users currently earn 1 coin for every successfully delivered order, which is reflected in the Past Orders page. Additionally, daily rewards are available on the Order page, offering users another fun and consistent way to earn coins. c) “Collections” in the nav bar allows users to redeem coins. After collecting coins, users can unlock various Smunch avatars as well as working towards unlocking a hidden word. d) We plan to expand the coin system further by introducing new ways to earn coins, such as daily games and referral rewards for inviting friends. Beyond unlocking avatars, future coin redemption options may include exclusive prizes, free delivery vouchers, and more exciting perks.
3) Weather-Aware Smunch Dino Status The backend integrates with NEA’s real-time weather API (https://data.gov.sg/collections/1459/view) to retrieve upcoming weather forecasts. The frontend continuously monitors weather changes and dynamically updates the Home Page with one of two themed dino avatars. Otherwise, a dino munching on a burger is shown, prompting users to start "SMUNCHIN" with our app.
4) Smunch Order Progress Bar When users place an order, a 4-step progress timeline visually guides them through the ordering process. The 4 steps are entering order details, selecting a delivery location, confirming the order, and completing payment.
5) Smunch Order Status Timeline in Receipts Both Active Orders and Past Orders receipts feature a dynamic order status timeline illustrated with Smunch dino avatars. These visuals clearly indicate each stage of the order process, from payment to completion.
6) “Forgot Password” Recovery To improve accessibility and reduce user frustration, we implemented a “Forgot Password” feature on the login page.
7) 404 Page Not Found If users navigate to an invalid or non-existent route (e.g., /hello), they are redirected to a custom 404 Page Not Found screen, enhancing the completeness of the smunch user interface
8) User Profile & Personalisation Users can upload/edit their profile pictures and can change their passwords as well.
9) Smunch AI Chatbot On the Order Page, users can easily access our Smunch chatbot by clicking the chatbot icon located at the bottom right corner of the screen. For a convenient shortcut, users can also use CTRL + SHIFT + D on their keyboard. Our AI Agent chatbot is built using workflow automation tool n8n, hosted on render. We extracted menu items and relevant data from our Supabase Database to train our model using Retrieval-Augmented Generation (RAG). Using n8n, the workflow we created automatically extracts text from a json or text file, splits it into smaller chunks, and generates vector embeddings using a local model that is deployed together with n8n. We pulled the large language model via Ollama. After multiple testing, we found that qllama/bge-large-en-v1.5:latest is the best model for our use case. These embeddings are then stored in a Qdrant vector database, also hosted together with n8n, enabling fast semantic search and future integration with AI-powered assistants. The chatbot is then prompt-engineered to be able to suggest or recommend meals based on our menu available to the end user. After trying numerous Ollama local large language model as the main model behind our SMUNCH AI Agent, and trying various cloud models ranging from Anthropic's and Deepseek's and Mistral's models, we found OpenAI's gpt-4o-mini to be the most reliable, fast and cost-effective and hence decided to use that as our main model.
10) OrderPage Unit Test
In Frontend/tests/unit/OrderPage.test.js, we have test cases that validates the core functionality of the OrderPage.vue component, focusing on dynamic rendering, challenge logic, and merchant grid behavior. It is important because it guarantees that users can interact with the daily challenges and merchants seamlessly, while backend integrations and game rewards perform as expected.
11) Authentication End-to-End (E2E) Test In Frontend/tests/e2e/authentication.ts, we have test cases that covers the complete authentication flow — from login and signup to route protection and logout. It is important because it provides confidence that user accounts, authentication flows, and secure pages behave consistently across sessions and devices.
Backend Technical Features
1) Email verification during signups Since signups are public facing and not moderated, we split the signing up into 2 steps. Entering the details on the frontend website through the sign up page. This sends the information to our backend, but the account is not created yet To ensure that the users actually have access to the email they are signing up with, we send them a signup link with the JWT token generated from their sign up as a parameter in the link. The account is only created once the link has been clicked, so we prevent fake accounts being created
2) Anti bot (cloudflare) To prevent our endpoints being spammed by bots, we implemented captcha on the signup page using turnstile (cloudflare), and in app rate-limiting for multiple endpoints which are likely to be susceptible to spams, for example signups and logins
3) Redis caching (Upstash) Redis was used in multiple places Security. In signups, creating a JWT with the information that the user passed (e.g. email, password) us would be unsafe, as any JWT can be decoded and the information may be leaked. So we stored this information in redis, and set the key to be randomly generated. This key was then what we put into the JWT, so that even if decoded there is nothing they can do with it. The rate-limiting middleware also used redis to store the number of times an endpoint was accessed by an IP address Speed. The main and usual purpose of redis is to avoid having to hit the database for every data fetch request. The places where this is used are where information would be accessed extremely commonly, and where information is not likely to change often, e.g. fetching of available merchants and their menus, as even a 1s delay would drastically affect the user experience, redis caching is crucial here. Error prevention. A possible place for error we identified was users double clicking buttons which would cause multiple requests being sent to the backend, most notably, the button to confirm your order, which would create your order in the backend. Previously, if you double clicked before the screen changed, duplicate orders would be created. Redis was used to store when an order creation request was sent from this user, set to expire in 3s.
4) Human error prevention during payment (QR code) A large number of random errors arise due to human error that we do not have to deal with. One way of handling this is through not having them fill in any information at all. Our QR code is generated such that when scanned, the exact payment amount and payment reference is filled in, so all the user has to do is tap (or swipe) to pay. The fields are also not alterable, so we avoid the issue of incorrect amount paid, as well as payments being unable to be detected due to incorrect payment reference (e.g. what we expect is SMUNCH123, users may put 123 or SMUNCH-123 or smunch 123). The QR codes are compatible with all banks.
5) Emailer (nodemailer) We implemented our own emailing system using nodemailer, which sends official emails from the SMUNCH email account. One example is the emailer is sending of receipts for confirmed orders. When the admin indicates that payment has come in, the email is sent as a confirmation for the user, which includes details of the order.
6) Payment Reminder emails (cronjob) Payment reminders are automated with cronjobs built using the node cronjob package. Reminders are crucial, yet we do not want to cause spam. After deliberation we have decided on the following logic for the reminder schedule. We will only ever send 2 email reminders. The 1st will be at 9pm the night before your order. The 2nd will be 40 minutes before your order is supposed to be delivered. The reason for 40 minutes is because we give the users 5 minutes to send payment, before we do our final checks and order distribution 35 minutes before delivery, so that our runners will have 30 minutes to fulfil the orders We store the timestamps of the reminders that we send, so that it will not resend in the event of server restart or other unforeseeable issues.
7) Supabase triggers and functions Automatic updating of the merchant’s “has_children” field. This field is used in the case of restaurant chains, we need to know if we need to display the children (stalls) or show the menu directly. As such, the field needs to always be updated, which is where the trigger comes in. When a merchant is added with a parent, it will call a function to set the “has_children” field in the parent to true, and when a merchant is deleted it will do a database check for any other children, if none then it sets that to false. This ensures that no matter how we code, this will never break. Using this infrastructure, we could even extend the hierarchy level further, not just parent-child, but grandparent-parent-child, and even more to support grouping by location, shopping mall etc Coins update. The rule is that when an order is complete, the user will gain +1 coin. The easiest and most unbreakable way is to use a trigger and function, which would detect for completed orders, and increment the coins
7) Admin functions The following endpoints were created to make the lives of admins easier. Retrieving all the payments to check for. We had tried automating this payment checking, but ran into an issue with parsing the html as it appeared inconsistent (only capturing the transaction 50% of the time) , so to ensure accuracy we will have an admin check the account. They would use this endpoint to know which reference number and the corresponding amount paid to check for. Once checked, there is an endpoint to batch confirm payments, which can be used for an admin dashboard. Efficiently splitting the orders based on the number of runners. We experimented with using a K-medoids algorithm to split the orders into clusters, where 1 cluster is 1 runner. They are split based on room proximity, to ensure that runners would be assigned runners that are in close proximity to each other. We used a heuristic to calculate distance values based on building, room type, floor and room number. After which a TSP heuristic algorithm with 2 opt would be used to provide the route which the runner should use for maximum (or near optimal) efficiency, since path finding for the absolute best path is computationally expensive. While the code logic is sound, there is a small bug so it is currently not in use.
8) Merchant endpoints (future developments) The end goal is for us to have results to show, to be able to convince merchants to join our platform. The groundwork has been laid for the implementation of this feature, with the functions for merchant onboarding, and configuring of their merchant (e.g. menus)
Built With
- ai
- express.js
- n8n
- ollama
- qdrant
- rag
- redis
- supabase
- vue.js
Log in or sign up for Devpost to join the conversation.