Project 05 · Full-Stack Web App
A full-stack academic-planning web app for York University students. A React single-page app talks to a Spring Boot REST API backed by MySQL, unifying course search, conflict-free scheduling, professor ratings, progress tracking and PDF export — with dedicated Student, Professor and Advisor roles.
EECS 3311 · Software Design · York University — Group 8
YUPlanner is a classic three-tier web application: a JavaScript SPA in the browser, a Java REST service, and a relational database. Rather than juggling several disconnected tools, a student can search the catalogue, check prerequisites, build a conflict-free timetable, review professor ratings, track progress, and export a schedule to PDF — all from one interface. The system serves three roles, each with a dedicated dashboard and navigation.
| Backend | Technology | Notes |
|---|---|---|
| Language | Java 17 | maven.compiler 17 |
| Framework | Spring Boot 3.3.5 | spring-boot-starter-parent |
| Web / REST | Spring Web (MVC) | embedded Tomcat |
| Persistence | Spring Data JPA · Hibernate | MySQLDialect |
| Build | Apache Maven | mvnw wrapper included |
| Frontend | Technology | Notes |
|---|---|---|
| Library | React 18.3.1 | Create React App (react-scripts 5) |
| Routing | react-router-dom 6.28 | client-side routing |
| UI kit | Material-UI 6.1.9 | + Emotion 11 styling |
| HTTP | Axios 1.7.8 + Fetch API | REST calls to backend |
| PDF export | jsPDF 2.5.2 | + autotable / html2canvas |
| Database | Value |
|---|---|
| Engine | MySQL 8.0.40 (InnoDB, utf8mb4) |
| Schema | yuplanner |
| JDBC URL | jdbc:mysql://localhost:3306/yuplanner |
| Schema strategy | spring.jpa.hibernate.ddl-auto=update |
| Seed data | backend/db/yuplanner.sql |
A three-tier architecture with a clear separation between presentation, application, and data. Presentation ↔ application is stateless HTTP with JSON bodies; application ↔ data is JDBC mediated by Hibernate.
@CrossOrigin)In the documented development configuration, all three tiers run on the developer's workstation.
A request flows straight down the stack and back: a React component calls the API (Axios/Fetch), Spring MVC routes it to the matching @RestController, which delegates to a service and then a Spring Data repository; Hibernate turns the entity operations into SQL over JDBC, and the JSON result propagates back up to re-render the component. The login and rating sequence diagrams (below) trace this concretely.
CORS: courseController restricts access to http://localhost:3000; the other controllers use a bare @CrossOrigin (all origins) — flagged in Limitations.
The backend is organised into four cooperating layers. A request enters at the controller layer and descends through service and repository layers to the entity/database layer; results return along the same path.
@RestController
@Entity
eecs3311.app.yuplanner
|-- YUPlannerApplication.java // @SpringBootApplication entry point, port 8080
|-- controller/ // @RestController REST endpoints
|-- service/ // business logic: interface + Impl
|-- repository/ // Spring Data JPA repositories
+-- model/ // @Entity JPA domain objects
| Repository | Derived query method |
|---|---|
| studentRepository | student findByUsernameAndPassword(String, String) |
| professorRepository | professor findByUsernameAndPassword(String, String) |
| courseRepository | course findByCourseCode(int) |
| RatingRepository | List<Rating> findByProfessor(professor) |
| advisorRepository | (none — inherited CRUD only) |
spring.datasource.url=jdbc:mysql://localhost:3306/yuplanner
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
All endpoints are served at http://localhost:8080; request/response bodies are JSON. Each controller declares its own CORS policy.
| Method | Path | Request | Response |
|---|---|---|---|
| POST | /student/add | student JSON | "New student is added" |
| GET | /student/getAll | — | List<student> |
| POST | /student/find | username, password | matching student, or null |
| GET | /course/getAll | — | List<course> |
| GET | /course/get/{courseCode} | path: int | matching course |
| POST | /professor/find | username, password | matching professor, or null |
| GET | /advisor/getAll | — | List<advisor> |
| GET | /rating/professor-reviews/{id} | path: int | List<Rating> · 404 if absent |
| POST | /rating/add | Rating JSON | "New rating is added" |
| GET | /rating/average/{id} | path: int | double average · 404 if absent |
{ "username": "mhart",
"password": "password" }
{ "id": 1, "userid": 123456789,
"firstname": "Maya", "lastname": "Hart",
"username": "mhart",
"courses": ["EECS 3311"] }
Two representative flows — the stateless login credential lookup, and the average-rating fetch (which raises a 404 via ResponseStatusException when the professor or reviews are absent):
Authentication is a stateless credential lookup — the SPA posts a username/password to /student/find or /professor/find and receives the matching record (or null). There is no token, session, or password hashing (see Limitations).
The domain is modelled by five JPA entities, each mapped to a MySQL table. The only database-enforced association is Rating → professor (@ManyToOne); every other cross-reference is stored as a plain string rather than a foreign key.
| Entity | Key fields | Seed rows |
|---|---|---|
| student | id PK · studentid · name · username/password · courses : List<String> (@ElementCollection) | 5 |
| course | id PK · courseCode · dept · coursename · day/time/duration · term/section/type · campus · prerequisites · instructor | 6 |
| professor | id PK · employeeid · name · username/password · courses : String | 4 |
| advisor | id PK · employeeid · name · username/password · students : String | 4 |
| Rating | id PK · professor (@ManyToOne, @JoinColumn professor_id) · rating : double · review : varchar(500) · studentName | — |
All other conceptual links — a student's courses, a professor's courses, an advisor's students — are stored as human-readable strings, so they are neither validated nor joined by the database.
The frontend is a React 18 SPA scaffolded with Create React App. The root App.js wraps the tree in a <UserProvider> (React Context) and a <BrowserRouter>, then declares all client-side routes. Cross-cutting user state (identity + role) is shared through Context rather than prop-drilling.
| Route | Component / purpose |
|---|---|
| /login · /signup | Login / Signup — authentication |
| /student-profile | StudentProfile — student dashboard |
| /student-profile/search-courses | SearchCourses — browse / filter catalogue |
| /student-profile/view-schedule | ViewFullSchedule — weekly schedule |
| /professor-profile | ProfessorProfile + ManageCourses / StudentsReviews |
| /advisor-profile | AdvisorProfile + AdvisorViewCourses / ViewStudents |
UserContext stores the authenticated user + role after login. Components reach the API at localhost:8080 via Axios and Fetch; the schedule view uses jsPDF (with autotable / html2canvas) to export a downloadable weekly timetable.
The UI uses York University's brand red (#E31837) as the primary accent, most visibly on the login screen.
(a) Login screen
(b) Student dashboard
Prerequisites: Java 17 + Maven (wrapper included), MySQL 8.0.40 on localhost:3306, and Node.js 22.11 LTS with npm 10.9.
# 1 · seed the database
mysql -u root -p < backend/db/yuplanner.sql
# 2 · backend → http://localhost:8080
cd backend/yuplanner && mvn spring-boot:run
# 3 · frontend → http://localhost:3000
cd frontend/yuplanner && npm install && npm start
| Component | Port | URL |
|---|---|---|
| React SPA (CRA dev server) | 3000 | http://localhost:3000 |
| Spring Boot REST API | 8080 | http://localhost:8080 |
| MySQL | 3306 | jdbc:mysql://localhost:3306/yuplanner |
Characteristic of an educational, sprint-scoped project — not broken features, but the items that matter most for security, correctness and maintainability.