NUSMods

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.

The official NUS timetable builder

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 Beng

v2 - 2014

NUSMods v2

Rebuilt with Backbone + Marionette. Added module reviews, CORS bidding data, venue finder. A real product now.

Beng Beng
Yang Shun Yang Shun

v3 - 2016

NUSMods v3

A complete rewrite in React + Redux. Mobile-first. Offline-capable. The version that's still running today.

Yang Shun Yang Shun
Li Kai Li Kai
GitHub Issue #294 - the one that started it all

Official Recognition - 2019

NUSMods presented at Academic Day.

We won the bid over Oracle.

The People

Chronological · avatar size ∝ code impact

Beng Beng
Yang Shun Yang Shun
Ashray Jain Ashray
Xinan Xinan
Ng Zhi An Zhi An
Li Kai Li Kai
Yi Jiang Yi Jiang
E-Liang E-Liang
Christopher Goh Chris Goh
Zhao Wei Zhao Wei
Jonathan Loh Jon Loh
Ravern Ravern
Kok Rui Kok Rui
Leslie Yip Leslie Yip
?
You?

2018–2026: Steady State

No more rewrites. Just more features.

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

2012 CORS scraping
2014 IVLE API
2018 Official NUS API with auth keys

Architecture

No database. JSON files served via reverse proxy + Cloudflare.

Streaming with oboe.js to handle large timetable datasets without blowing memory.

Yi Jiang Yi Jiang
E-Liang E-Liang
React

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.

// NUSMods was written with class components
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.

React

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.

Redux

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.

UI Timetable: empty Action waiting... Reducer (state, action) → state Store { modules: [] }

Click a module to dispatch an action and watch it flow through. One-way data flow - easy to debug, test, and persist.

Redux

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.

// reducers/index.ts - simplified
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

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.

NUSMods GitHub repo

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 Li Kai
Yi Jiang Yi Jiang
E-Liang E-Liang
Leslie Yip Leslie Yip
Jonathan Loh Jonathan Loh

How You Can Help

Programmers Designers UX Researchers
NUSMods

NUSMods is yours now.

Every version was someone's first big project. This one could be yours.