14 Years of Student-Built Software
A React app from 2016 still runs in production, mostly unchanged.
40,000+ users.
639 stars · 345 forks · 90+ contributors · Since 2012
No company. No funding. Just students passing it on.
This was the alternative.
v0.1 - CORSet
Before NUSMods, there was CORSet - a CORS bidding helper.
v1 - 2012
The official timetable was unusable. Beng built a jQuery replacement in a dorm room.
Beng
v2 - 2014
Rebuilt with Backbone + Marionette. Added module reviews, CORS bidding data, venue finder. A real product now.
Beng
Yang Shun
v3 - 2016
A complete rewrite in React + Redux. Mobile-first. Offline-capable. The version that's still running today.
Yang Shun
Li Kai
Official Recognition - 2019
NUSMods presented at Academic Day.
We won the bid over Oracle.
The People
Chronological · avatar size ∝ code impact
Beng
Yang Shun
Ashray
Xinan
Zhi An
Li Kai
Yi Jiang
E-Liang
Chris Goh
Zhao Wei
Jon Loh
Ravern
Kok Rui
Leslie Yip
2018–2026: Steady State
No more rewrites. Just more features.
- + Today Page - daily schedule with venue maps
- + Degree Planner - multi-year planning with prereq checks
- + Elasticsearch-powered search
- + Dark mode & system color scheme
- + Prerequisite tree (ANTLR rewrite)
- + Timetable Optimizer (beta)
- + TA mode
- + CPEx integration
- + ModReg notifications
- + A hidden Tetris game
Student-owned software that the school wouldn't replace.*
Architecture
React + Redux SPA
Class components, CSS Modules, Webpack bundle. All timetable logic runs client-side.
Export Service (Koa)
Timetable image/PDF exports via Puppeteer. Elasticsearch for search.
Static Data via Reverse Proxy
Module JSON as flat files. Served through a reverse proxy, cached by Cloudflare.
The heavy lifting is client-side. The backend is thin and focused on data.
Stack Evolution
| v1 (2012) | v2 (2014) | v3 (2016) | v3... still (2026) | |
|---|---|---|---|---|
| Framework | jQuery | Backbone / Marionette | React / Redux | React / Redux |
| Styling | CSS | LESS | CSS Modules + SCSS | CSS Modules + SCSS |
| Backend | Apache + PHP | Static JSON via proxy | Static JSON via proxy | Static JSON + Koa export |
| Build | - | Grunt | Webpack | Webpack |
| Language | ES5 | ES5 | ES6 + Flow | TypeScript |
| Tests | - | - | Jest | Jest |
The Data Pipeline
Evolution
Architecture
No database. JSON files served via reverse proxy + Cloudflare.
Streaming with oboe.js to handle large timetable datasets without blowing memory.
Yi Jiang
E-Liang
What is React?
A JavaScript library for building UIs out of components. Instead of manipulating the DOM directly, you describe what the UI should look like, and React figures out how to update it.
class Timetable extends React.Component {
state = { semester: 1 };
render() {
return <div>Sem {this.state.semester}</div>;
}
}
In 2016 this was the only way to write React. Classes hold state, lifecycle methods control when things happen.
Function Components
The modern way to write React. Instead of classes, you write plain functions.
Hooks like useState and useEffect replace state and lifecycle methods.
Class component (2016)
class Counter extends React.Component {
state = { count: 0 };
render() {
return (
<button onClick={() =>
this.setState({ count:
this.state.count + 1 })}>
{this.state.count}
</button>);
}
}
Function component (today)
function Counter() {
const [count, setCount]
= useState(0);
return (
<button onClick={() =>
setCount(count + 1)}>
{count}
</button>);
}
Same result, less boilerplate. NUSMods was written with classes but the patterns still apply.
What is Redux?
A predictable state container. All app state lives in one store. Components dispatch actions, pure reducers compute the next state, and the UI re-renders.
Click a module to dispatch an action and watch it flow through. One-way data flow - easy to debug, test, and persist.
Redux Superpowers
Because all state is serializable and centralized, NUSMods gets powerful features almost for free.
Persistence
Every reducer wrapped with persistReducer - timetable survives reloads.
Undo
Custom undoHistory reducer watches REMOVE_MODULE / SET_TIMETABLE. Ctrl+Z restores module + colors.
URL Sharing
Same serializable state powers UI, localStorage, and URL query strings.
Year Migration
Custom stateReconciler auto-archives old timetables when the academic year changes.
const undoConfig = {
limit: 1,
actionsToWatch: [REMOVE_MODULE, SET_TIMETABLE],
};
export default combineReducers({
timetables: persistReducer(config,
undoHistory(undoConfig, timetableReducer)),
});
The Problem with CSS
CSS is global by default. Two components using .container? They clash.
The traditional fix was BEM - Block, Element, Modifier.
BEM naming convention
.timetable { } /* Block */
.timetable__lesson { } /* Element */
.timetable__lesson--active { } /* Modifier */
BEM works, but it's manual and verbose. You're encoding scope into the name yourself. What if the tooling could do it for you?
CSS Modules
Each component gets its own scoped CSS file. Webpack hashes class names at build time - automatic scoping, no naming conventions needed.
Timetable.scss
.container { display: grid; }
.lesson { border-radius: 4px; }
Timetable.tsx
import styles from './Timetable.scss';
<div className={styles.container}>
.container becomes .Timetable_container_a3f2d in the output. Simple names, no collisions, no BEM.
Why It Lasted
- ✓ Thin backend - Koa handles what the client can't
- ✓ React + Redux became the industry standard
- ✓ CSS Modules - scoped styles, no global conflicts
- ✓ Tests + CI + type checking (Flow → TypeScript)
- ✓ Static data via reverse proxy + Cloudflare - cacheable, zero-ops
- ✓ Boring technology wins
Automation
A student team running production software for 40,000 users.
Data Scrapers
Run automatically every semester
CI/CD
Pipeline on every push
Sentry
Error tracking in production
BrowserStack
Cross-browser testing
Commercial-grade tooling, free for open source.
The Codebase is Approachable
Because the architecture is simple, you can contribute without understanding everything. Pick an issue. Ship a feature.
Degree Planner
The architecture scaled to a second major feature without a rewrite.
Drag-and-drop
Multi-year planning with react-beautiful-dnd
Persisted State
Own reducer with state migrations
Import / Export
Custom modules support
Prerequisite Checking
Validates module dependencies
Li Kai
Yi Jiang
E-Liang
Leslie Yip
Jonathan Loh
How You Can Help
NUSMods is yours now.
Every version was someone's first big project. This one could be yours.