An app for viewing NC State Dining menus and hours in one place.
| Home | Menu List | Menu |
|---|---|---|
![]() |
![]() |
![]() |
| Item History | Favorites | Event Details |
|---|---|---|
![]() |
![]() |
![]() |
NC State Dining recently switched their menus to a new platform, NetNutrition. While the new site is functional, it means that hours and menus are in different places, and the new website suffers from UX issues. That's why we set out to build an app that was fast, streamlined, and personalizable — an app that gives you the information you need and gets out of your way.
- View NC State Dining locations with daily hours and location photos
- Support for adjusted hours due to holidays or inclement weather
- Dining menus with dietary restriction information and relevant categories surfaced first
- Mark menu items as favorites to find them easily and get notified when they're on today's menu
- View upcoming NC State Dining events in the app
- See a menu item's history to predict when it will be offered next
- Offline browsing after a menu has been downloaded once
- Speedy interface with caching and speculative loading of common pages
- Light/dark theme and Material You support
The FoodFinder backend is a Kotlin application that runs a Ktor web server and a scraper. The scraper runs once daily, collecting the dining menus from NetNutrition and the hours from the NC State Dining website. It stores those in a local SQLite database. Then, it queries that database to respond to client requests.
When designing the backend, extra care was taken to be polite to the upstream data sources. The amount of simultaneous inflight requests is limited, and the data is fetched on a schedule rather than on-demand.
The frontend is an Android app built with Kotlin and Jetpack Compose. It fetches information as JSON from the backend and displays it in a nice user interface.
Android app:
- Kotlin
- Jetpack Compose, Compose Navigation
- Hilt (dependency injection)
- Retrofit + OkHttp (API client with HTTP caching), Coil (image loading)
- Detekt (static analysis)
Backend:
- Ktor (web server)
- SQLite (flatfile database)
- Exposed (ORM)
- OkHttp (scraper HTTP client), Ksoup (HTML parsing)
- Quartz (scheduled jobs), kotlinx.serialization (JSON)
- Docker (containerization/deployment)
Base URL (production): https://foodfinder.appdevncsu.org. Routes are defined in backend/src/main/kotlin/org/appdevncsu/foodfinder/server/Server.kt.
| Method | Path | Query params | Description |
|---|---|---|---|
GET |
/api/locations |
— | List menu locations (id, name, slug, type, imageUrl) |
GET |
/api/locations/{slug}/image |
— | Get a proxied/cached location photo |
GET |
/api/locations/{locationId}/menus |
— | List upcoming menus for a location |
GET |
/api/locations/{locationId}/menus/{menuId} |
— | Get sections + items (with dietary flags and isNew) for one menu |
GET |
/api/locations/{locationId}/item-history |
name (required), date (optional, defaults to latest menu date) |
Dates an item was served in the 90 days ending on date, plus first-seen and weekly frequency |
GET |
/api/hours |
days=N (optional, 1–7, defaults to 3) |
Hours per location for the next N days (today Eastern onward) |
GET |
/api/events |
— | Upcoming campus dining events (soonest 20), sourced from the dining.ncsu.edu calendar and cached for a day |
QUERY |
/api/favorites/menus |
JSON body: items (names), days (default 1) |
Upcoming menus containing any of the requested favorite items |
The API currently has no authorization mechanism. Feel free to use it for your own projects, as long as you set a distinct, custom User-Agent and respect the Cache-Control headers we set on our responses.
.
├── android/ # Android app (Kotlin, Jetpack Compose)
│ ├── app/src/main/ # UI, ViewModels, API client
├── backend/ # Ktor API server + scrapers (Kotlin/JVM)
│ ├── src/main/kotlin/org/appdevncsu/foodfinder/
│ │ ├── scraper/ # NetNutrition + dining.ncsu.edu scrapers
│ │ ├── server/ # API routes, image proxy, daily-scrape scheduler
│ │ └── shared/ # SQLite/Exposed database + data models
│ ├── config/deploy.yml # Kamal deploy config
│ └── Dockerfile
└── frontend/ # Marketing website (Astro), served by the backend
└── src/pages/ # Pages, e.g. the landing page and privacy policy
- JDK 24 for the backend
- Android Studio (with JDK 21+) for the app
- Docker (optional, for running/deploying the backend container)
cd backend
./gradlew shadowJar
java -jar build/libs/*-all.jar <command>
# or: ./gradlew run --args "<command>"
# e.g. ./gradlew run --args "scrape events"Available commands:
| Command | Purpose |
|---|---|
scrape [target] |
Run a scrape once and exit. target is all (default), menus, hours, or events |
serve |
Run the API server on port 3000 |
serve-scheduled |
Run the API server and scrape daily at 6am Eastern Time |
You can build the FoodFinder backend and marketing site as a Docker image. From the repo root:
docker build -f backend/Dockerfile -t foodfinder-backend .
docker run -p 3000:3000 -v foodfinder_data:/data foodfinder-backend
# curl http://localhost:3000/api/locations
# open http://localhost:3000/In production, we use Kamal (see backend/config/deploy.yml) to deploy to https://foodfinder.appdevncsu.org.
The marketing site in frontend/ is an Astro project. It is built into the backend image and served by the Ktor server.
cd frontend
npm install
npm run dev # local dev server with hot reload
npm run build # static output in frontend/distTo serve the built site locally through the backend, run npm run build and then start the server with the STATIC_DIR=frontend/dist environment variable.
Open android/ in Android Studio and run the app configuration, or from the command line:
cd android
./gradlew assembleDebugThe app points at the production API (https://foodfinder.appdevncsu.org in android/app/src/main/java/org/appdevncsu/foodfinder/data/APIClient.kt) by default. For a signed release build, copy android/keystore.properties.example to android/keystore.properties and fill in the values (or set the RELEASE_STORE_* / RELEASE_KEY_* env vars).
The Android app uses Detekt for linting with Compose rules.
It's configured in android/detekt.yml, and you can run it locally like this:
cd android
./gradlew detektFoodFinder was developed from Fall 2025 — Spring 2026 by the Android team of the App Development Club at NC State:
- Brendan Swanson
- Dylan Lester
- Daniel Flores Elizondo
- Jonathan Duran-Ortiz
- Gauri Subash
- Venkat Vulava
This is not an official NC State University project. It is not affiliated with NC State University or Illumia (formerly Transact + CBORD, the company behind NetNutrition).
All information displayed in the app is publicly available online, and is gathered freely without bypassing any protection measures.





