MITS Academy — Java Full Stack Development
MITS ACADEMY
Java Full Stack Development
Spring Boot + React + MySQL — Complete Course
[Link]
Page 1 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
Chapter 1: Java Full Stack Overview
Full-stack development means owning every layer of an application — from the database that
persists data, through the server-side business logic, all the way to the browser-rendered user
interface. In the Java ecosystem this trinity is realised with MySQL as the relational database,
Spring Boot as the backend framework, and React as the frontend library. Each layer
communicates through well-defined contracts, making the system easy to test, scale, and
maintain independently.
The backbone of communication between backend and frontend is the REST (Representational
State Transfer) architectural style. A REST API exposes resources — such as /students,
/courses, /results — over HTTP. Clients perform CRUD operations by sending GET, POST,
PUT, and DELETE requests and receive JSON responses. Because HTTP is stateless, every
request carries all the information the server needs, which allows horizontal scaling without
sticky sessions.
Spring Boot dramatically reduces configuration overhead compared to traditional Spring MVC. It
provides an embedded Tomcat server, auto-configuration, and a Maven (or Gradle) build
system. Maven organises your project into well-known phases: compile, test, package, and
install. The resulting artefact is a single self-contained JAR that you can deploy on any machine
with a JRE — no application server required.
A well-designed Spring Boot application separates responsibilities into three layers: the
Controller layer accepts HTTP requests and returns responses; the Service layer contains
business rules and orchestrates operations; the Repository layer (powered by Spring Data JPA)
speaks directly to the database. This layered architecture enforces the Single Responsibility
Principle and makes unit-testing each layer in isolation straightforward.
1.1 Application Layer Diagram
• Controller Layer — @RestController, handles HTTP, delegates to Service
• Service Layer — @Service, business logic, transaction management
• Repository Layer — @Repository / JpaRepository, database access via JPA
• Entity Layer — @Entity, maps Java class to database table
• Database — MySQL, managed by Hibernate ORM
1.2 Technology Stack Summary
• Backend : Java 17+, Spring Boot 3.x, Spring Data JPA, Spring Security, JWT
• Frontend: React 18, Vite, React Router v6, Axios, React Hook Form, [Link]
• Database: MySQL 8.x, Hibernate, Flyway migrations
• Build : Maven (backend), npm/Vite (frontend)
• Deploy : Ubuntu VPS, Nginx reverse proxy, Certbot SSL, PM2
1.3 Comprehensive Example — Minimal Full-Stack Skeleton
// ── FILE: src/main/java/com/example/demo/[Link] ──────────────
// This is the entry point of the entire Spring Boot application.
// @SpringBootApplication is a meta-annotation that combines:
// @Configuration – marks this as a source of Spring bean definitions
Page 2 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
// @EnableAutoConfiguration – tells Spring Boot to configure beans
automatically
// @ComponentScan – scans the current package and sub-packages for
components
package [Link];
import [Link];
import [Link];
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
// [Link]() bootstraps the embedded Tomcat server,
// loads the ApplicationContext, and starts listening on port 8080.
[Link]([Link], args);
}
}
// ── FILE: src/main/java/com/example/demo/model/[Link] ────────────────
// An @Entity class maps directly to a database table named 'student'.
// Hibernate reads the annotations at startup and creates/validates the schema.
package [Link];
import [Link].*;
@Entity // marks this POJO as a JPA entity
@Table(name = "student") // explicit table name (optional — defaults to
class name)
public class Student {
@Id // primary key
@GeneratedValue(strategy = [Link]) // auto-increment
private Long id;
private String name;
private String email;
// JPA requires a no-arg constructor for proxy creation
public Student() {}
public Student(String name, String email) {
[Link] = name;
[Link] = email;
}
// Getters & setters (omitted for brevity — use Lombok @Data in real
projects)
public Long getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public void setName(String name) { [Link] = name; }
public void setEmail(String email) { [Link] = email; }
}
// ── FILE: src/main/resources/[Link] ───────────────────────
// [Link]=jdbc:mysql://localhost:3306/fullstack_db
// [Link]=root
// [Link]=secret
// [Link]-auto=update // auto-creates/updates tables
// [Link]-sql=true // prints SQL to console for
debugging
Page 3 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
1.4 Common Mistakes
• Forgetting @Entity or @Table on a model class — Hibernate will not map it.
• Circular dependencies: Controller -> Service -> Controller. Always inject in one direction.
• Returning the entity directly from the controller exposes internal fields. Use DTOs instead.
• Using ddl-auto=create in production — it drops and recreates tables on every restart.
1.5 Practice Exercises
• Draw the three-layer architecture for a Library Management System.
• List five REST endpoints for a /books resource with their HTTP verbs.
• Identify which Spring annotation belongs to which layer: @RestController, @Service,
@Repository.
Chapter 2: Spring Boot Project Setup
The fastest way to bootstrap a Spring Boot project is [Link], the official project initialiser
maintained by the Spring team. You select the build tool (Maven or Gradle), the language (Java,
Kotlin, or Groovy), the Spring Boot version, and your project metadata (Group, Artifact,
Package). You then add dependencies from a curated list — Spring Web, Spring Data JPA,
MySQL Driver, Spring Security, and so on — and download a pre-configured ZIP.
The [Link] file is the heart of a Maven project. It declares the project's coordinates (groupId,
artifactId, version), the parent POM (spring-boot-starter-parent which provides sensible
dependency versions), and dependencies. Spring Boot starters are curated bundles: spring-
boot-starter-web includes Tomcat, Spring MVC, and Jackson (JSON); spring-boot-starter-data-
jpa includes Hibernate and Spring Data. Using starters means you rarely need to manage
individual library versions.
The [Link] (or [Link]) file externalises configuration so that the same
JAR can behave differently in development, testing, and production. Spring Boot supports
profiles: [Link], [Link]. You activate a profile with the
environment variable SPRING_PROFILES_ACTIVE=prod. Sensitive values such as database
passwords should be injected as environment variables rather than hard-coded in the file.
Understanding the auto-configuration mechanism is key to debugging Spring Boot. When you
add spring-boot-starter-data-jpa to the classpath, Spring Boot's auto-configuration detects it and
automatically creates a DataSource, EntityManagerFactory, and TransactionManager. You can
see what was auto-configured and why by running the application with --debug and inspecting
the "CONDITIONS EVALUATION REPORT" in the console output.
2.1 Required Dependencies ([Link])
• spring-boot-starter-web — REST controllers, embedded Tomcat
• spring-boot-starter-data-jpa — Hibernate, Spring Data repositories
• spring-boot-starter-security — authentication & authorisation
• spring-boot-starter-validation — Bean Validation (@Valid, @NotBlank)
• spring-boot-starter-mail — JavaMailSender
• mysql:mysql-connector-j — MySQL JDBC driver
• [Link]:jjwt-* — JWT creation and parsing
Page 4 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
• [Link]:lombok — @Data, @Builder, @Slf4j (optional)
• [Link]:flyway-core — database migrations
2.2 Comprehensive Example — [Link] + [Link]
<!-- ── FILE: [Link] ──────────────────────────────────────────────────── -->
<!--
The parent POM provides:
- Dependency management (curated versions that work together)
- Plugin management (maven-compiler-plugin, spring-boot-maven-plugin)
- Resource filtering for [Link]
-->
<project xmlns="[Link]
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version> <!-- Always use a recent stable release -->
</parent>
<groupId>[Link]</groupId>
<artifactId>student-portal</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging> <!-- Produces a self-contained executable JAR
-->
<properties>
<[Link]>17</[Link]> <!-- Spring Boot 3 requires Java 17+ -->
</properties>
<dependencies>
<!-- Web layer: REST controllers + embedded Tomcat -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Data layer: Hibernate + Spring Data JPA -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL JDBC driver (runtime only — not needed for compilation) -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Bean Validation: enables @Valid, @NotBlank, @Email etc. -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok: reduces boilerplate (getters, setters, constructors) -->
<dependency>
Page 5 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
<groupId>[Link]</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional> <!-- not included in the final JAR -->
</dependency>
<!-- Test support: JUnit 5 + Mockito + MockMvc -->
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Packages the project as a self-contained executable JAR -->
<plugin>
<groupId>[Link]</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- Exclude Lombok from the final JAR — it is only needed at compile
time -->
<excludes>
<exclude>
<groupId>[Link]</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
# ── FILE: src/main/resources/[Link] ────────────────────────
# Server configuration
[Link]=8080
# DataSource — values should come from environment variables in production
[Link]=jdbc:mysql://${DB_HOST:localhost}:3306/$
{DB_NAME:fullstack_db}
[Link]=${DB_USER:root}
[Link]=${DB_PASS:secret}
[Link]-class-name=[Link]
# JPA / Hibernate
[Link]-auto=validate # use 'update' in dev, 'validate'
in prod
[Link]-sql=true # print SQL statements to console
[Link].format_sql=true # pretty-print multi-line SQL
# Jackson: serialize Java dates as ISO-8601 strings
[Link]-dates-as-timestamps=false
2.3 Common Mistakes
• Missing spring-boot-starter-parent — without it you must manage all versions manually.
• Using ddl-auto=create-drop in staging — wipes the database on every redeploy.
Page 6 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
• Hard-coding passwords in [Link] — commit to Git and credentials are
exposed.
• Wrong Java version: Spring Boot 3 requires Java 17+. Using Java 11 causes startup
failure.
2.4 Practice Exercises
• Generate a new Spring Boot project at [Link] with Web, JPA, MySQL, and
Validation.
• Add a profile [Link] with ddl-auto=update and confirm it loads.
• Replace the hard-coded DB password with an environment variable and test locally.
Chapter 3: Spring Boot REST Controllers
A REST controller is the entry point through which HTTP clients interact with your application. In
Spring Boot, a class annotated with @RestController is automatically detected during
component scanning. @RestController is shorthand for @Controller + @ResponseBody, which
means every method return value is serialised directly to the HTTP response body as JSON
(using Jackson) rather than being interpreted as a view name.
Spring provides dedicated mapping annotations for each HTTP verb: @GetMapping for reads,
@PostMapping for creates, @PutMapping for full updates, @PatchMapping for partial updates,
and @DeleteMapping for deletions. Each annotation accepts a path template. Path variables —
dynamic segments such as /students/{id} — are extracted with @PathVariable. Query
parameters such as /students?name=Alice are extracted with @RequestParam.
The @RequestBody annotation tells Spring to deserialise the incoming JSON request body into
a Java object. It works in conjunction with @Valid to trigger Bean Validation before the method
body runs — if validation fails, Spring throws a MethodArgumentNotValidException which you
can handle centrally. ResponseEntity<T> gives you full control over the HTTP status code and
headers, making it the preferred return type for REST controllers.
HTTP status codes are a contract between server and client. 200 OK is returned for successful
GETs and PUTs; 201 Created for successful POSTs (with a Location header pointing to the
new resource); 204 No Content for successful DELETEs; 400 Bad Request for validation errors;
404 Not Found when a resource does not exist; 409 Conflict for duplicate key violations; 500
Internal Server Error for unhandled exceptions. Using correct codes allows client-side code and
API consumers to handle responses generically.
3.1 CRUD Endpoint Summary
• GET /api/students — list all students
• GET /api/students/{id} — get one student by ID
• POST /api/students — create a new student
• PUT /api/students/{id} — update an existing student
• DELETE /api/students/{id} — delete a student
3.2 Comprehensive Example — StudentController
// ── FILE: src/main/java/com/mits/controller/[Link] ─────────
Page 7 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
// @RestController = @Controller + @ResponseBody
// Every method return value is serialised to JSON automatically.
@RestController
// @RequestMapping sets a base path for all methods in this class.
// Best practice: version your API (v1) from day one.
@RequestMapping("/api/v1/students")
// @CrossOrigin allows requests from the React dev server (port 5173).
// In production, configure this in a global WebMvcConfigurer instead.
@CrossOrigin(origins = "[Link]
public class StudentController {
// Constructor injection is preferred over @Autowired field injection:
// - Makes dependencies explicit and easier to test
// - Works with final fields (immutability)
private final StudentService studentService;
public StudentController(StudentService studentService) {
[Link] = studentService;
}
// ── GET /api/v1/students ────────────────────────────────────────────────
// Returns a list of ALL students. In production, add pagination:
// @GetMapping + Pageable parameter + Page<StudentDTO> return type.
@GetMapping
public ResponseEntity<List<StudentDTO>> getAllStudents() {
List<StudentDTO> students = [Link]();
// [Link]() wraps the body with HTTP 200
return [Link](students);
}
// ── GET /api/v1/students/{id} ───────────────────────────────────────────
// @PathVariable binds the {id} segment from the URL to the method
parameter.
@GetMapping("/{id}")
public ResponseEntity<StudentDTO> getStudentById(@PathVariable Long id) {
StudentDTO student = [Link](id);
// Service throws ResourceNotFoundException if not found;
// that exception is mapped to 404 by the global exception handler.
return [Link](student);
}
// ── POST /api/v1/students ───────────────────────────────────────────────
// @RequestBody deserialises the JSON body into StudentDTO.
// @Valid triggers Bean Validation (@NotBlank, @Email, etc.) on StudentDTO.
@PostMapping
public ResponseEntity<StudentDTO> createStudent(@Valid @RequestBody
StudentDTO dto) {
StudentDTO created = [Link](dto);
Page 8 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
// HTTP 201 Created is the correct status for resource creation.
// [Link](CREATED).body(...) is more explicit
than .ok(...)
return [Link]([Link]).body(created);
}
// ── PUT /api/v1/students/{id} ───────────────────────────────────────────
// PUT replaces the entire resource. If only some fields should change, use
PATCH.
@PutMapping("/{id}")
public ResponseEntity<StudentDTO> updateStudent(
@PathVariable Long id,
@Valid @RequestBody StudentDTO dto) {
StudentDTO updated = [Link](id, dto);
return [Link](updated);
}
// ── DELETE /api/v1/students/{id} ────────────────────────────────────────
// HTTP 204 No Content means: "success, but there is no body to return."
// Avoid returning 200 with a message like "Deleted successfully" — that is
// non-standard and makes automated clients parse an unnecessary body.
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteStudent(@PathVariable Long id) {
[Link](id);
return [Link]().build();
}
}
3.3 Common Mistakes
• Returning the Entity directly instead of a DTO — exposes internal fields and causes lazy-
loading JSON serialisation errors.
• Using @Autowired on a field instead of constructor injection — hides dependencies and
breaks unit tests.
• Returning 200 for a newly created resource instead of 201 — breaks clients that check
status codes.
• Not handling exceptions — unhandled RuntimeExceptions return an ugly 500 stack trace
to the client.
3.4 Practice Exercises
• Add a GET /api/v1/students/search?name=Alice endpoint using @RequestParam.
• Change the POST handler to return a Location header pointing to
/api/v1/students/{newId}.
• Write a unit test for deleteStudent using MockMvc and verify 204 is returned.
Chapter 4: Spring Data JPA
Spring Data JPA sits on top of the Java Persistence API (JPA), which is itself an abstraction
over JDBC. Hibernate is the most widely used JPA implementation and is included with spring-
boot-starter-data-jpa. The central concept is the Entity — a Java class that is mapped to a
Page 9 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
database table. Hibernate reads annotations on the class at startup to understand the mapping
and generates DDL (CREATE TABLE ...) statements accordingly.
The JpaRepository interface is the cornerstone of Spring Data. When you declare an interface
that extends JpaRepository<EntityType, IdType>, Spring Data generates a full implementation
at runtime using dynamic proxies. You get findAll(), findById(), save(), deleteById(), count(), and
many more methods for free. You do not write a single line of SQL for basic CRUD operations.
Spring Data's query derivation mechanism allows you to express database queries simply by
naming repository methods correctly. findByNameContainingIgnoreCase(String name)
generates SELECT * FROM student WHERE LOWER(name) LIKE LOWER('%name%'). The
keywords — findBy, countBy, existsBy, deleteBy — combined with field names and operators
(Containing, GreaterThan, Between, OrderBy) cover the vast majority of query needs. For
complex queries, use the @Query annotation with JPQL or native SQL.
JPQL (Java Persistence Query Language) is object-oriented SQL — you query entity classes
and their fields, not table names and column names. This gives you database portability (the
same JPQL query works on MySQL, PostgreSQL, and H2). When you need database-specific
features — full-text search, window functions, stored procedures — switch to nativeQuery = true
in @Query and write plain SQL.
4.1 Common Query Derivation Keywords
• findByEmail(String email) — WHERE email = ?
• findByNameContainingIgnoreCase(String name) — WHERE LOWER(name) LIKE
LOWER(%?%)
• findByAgeGreaterThan(int age) — WHERE age > ?
• findByActiveTrueOrderByNameAsc() — WHERE active = 1 ORDER BY name ASC
• countByDepartment(String dept) — SELECT COUNT(*) WHERE department = ?
• existsByEmail(String email) — SELECT EXISTS(WHERE email = ?)
• deleteByIdAndActive(Long id, boolean active)— DELETE WHERE id = ? AND active = ?
4.2 Comprehensive Example — Student Entity + Repository
// ── FILE: src/main/java/com/mits/model/[Link] ────────────────────────
package [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Entity
@Table(
name = "student",
// Unique constraint at the DB level — prevents duplicate emails
// even if two concurrent requests slip past application-level checks.
uniqueConstraints = @UniqueConstraint(columnNames = "email")
)
@Data // Lombok: generates getters, setters, equals, hashCode,
toString
@NoArgsConstructor // JPA requires a no-arg constructor
Page 10 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
@AllArgsConstructor // convenient constructor for testing
public class Student {
@Id
@GeneratedValue(strategy = [Link]) // MySQL AUTO_INCREMENT
private Long id;
@NotBlank(message = "Name is required")
@Column(nullable = false, length = 100)
private String name;
@Email(message = "Must be a valid email address")
@NotBlank(message = "Email is required")
@Column(nullable = false, unique = true)
private String email;
// @Enumerated maps a Java enum to a database column.
// [Link] stores "ACTIVE"/"INACTIVE" instead of 0/1 —
// much easier to read in the database and survives enum reordering.
@Enumerated([Link])
@Column(nullable = false)
private StudentStatus status = [Link];
@Column(name = "enrollment_date")
private LocalDate enrollmentDate;
// @ManyToOne: many students can belong to one department.
// [Link] means the department is NOT loaded until accessed —
// avoids N+1 query problems and unnecessary joins for simple student
lists.
@ManyToOne(fetch = [Link])
@JoinColumn(name = "department_id")
private Department department;
public enum StudentStatus { ACTIVE, INACTIVE, GRADUATED }
}
// ── FILE: src/main/java/com/mits/repository/[Link] ─────────
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
// No @Repository annotation needed — Spring Data detects this interface
// automatically and generates a full implementation at runtime.
public interface StudentRepository extends JpaRepository<Student, Long> {
// ── Derived query methods ─────────────────────────────────────────────
Optional<Student> findByEmail(String email); // returns empty Optional if
not found
List<Student> findByStatus([Link] status);
// Spring Data generates: WHERE LOWER(name) LIKE LOWER('%name%')
List<Student> findByNameContainingIgnoreCase(String name);
Page 11 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
// ── JPQL @Query — queries entity classes, not table names ─────────────
// :deptId is a named parameter bound via @Param
@Query("SELECT s FROM Student s WHERE [Link] = :deptId AND
[Link] = 'ACTIVE'")
List<Student> findActiveStudentsByDepartment(@Param("deptId") Long deptId);
// ── Native SQL @Query — use only when JPQL cannot express the query ───
// nativeQuery = true tells Spring to send the SQL directly to MySQL
@Query(
value = "SELECT * FROM student WHERE YEAR(enrollment_date) = :year",
nativeQuery = true
)
List<Student> findEnrolledInYear(@Param("year") int year);
// Aggregation: count students per status
@Query("SELECT [Link], COUNT(s) FROM Student s GROUP BY [Link]")
List<Object[]> countByStatus();
}
4.3 Common Mistakes
• Using [Link] everywhere — causes N+1 queries and extremely slow
endpoints.
• Forgetting @Transactional on methods that modify data — updates may silently not
persist.
• Querying table names in JPQL (SELECT * FROM student) instead of entity names
(SELECT s FROM Student s).
• Calling repository methods from within the same @Service class transactional method
and expecting lazy fields to load — session may already be closed.
4.4 Practice Exercises
• Add a method findByDepartmentNameAndStatus to StudentRepository using query
derivation.
• Write a @Query that returns students who have not enrolled in any course.
• Enable Spring Data auditing (@CreatedDate, @LastModifiedDate) on the Student entity.
Chapter 5: MySQL Database Integration
MySQL is a battle-tested open-source relational database that integrates seamlessly with
Spring Boot via JDBC and Hibernate. During development, you can let Hibernate auto-generate
the schema (ddl-auto=update), which is convenient but dangerous in production because
schema changes are not versioned or repeatable. For production systems, you should manage
schema evolution with a migration tool such as Flyway or Liquibase.
Relational data almost always involves associations between tables. JPA models these with
@OneToOne, @OneToMany, @ManyToOne, and @ManyToMany. The most common
association is @OneToMany: one Department has many Students. On the owning side
(Student), you declare @ManyToOne and a foreign-key column. On the inverse side
(Department), you declare @OneToMany(mappedBy = "department"). The mappedBy attribute
tells JPA that the Student entity owns the relationship and controls the foreign key column.
Page 12 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
Flyway works by placing SQL migration scripts in src/main/resources/db/migration. Each script
is named V{version}__{description}.sql (e.g., V1__create_student_table.sql). Flyway tracks
which scripts have been applied in a flyway_schema_history table and runs only new scripts on
startup. This gives you an auditable, repeatable, and team-friendly way to evolve the database
schema. In CI/CD pipelines, migrations run automatically before the application starts.
Connection pooling is critical for performance. Spring Boot auto-configures HikariCP — the
fastest JDBC connection pool available for Java. A connection pool maintains a set of open
database connections that are reused across requests, avoiding the expensive overhead of
opening a new connection for every query. You should tune [Link]-
pool-size based on your database server's max_connections setting and expected concurrent
load.
5.1 Flyway Migration Naming Convention
• V1__create_student_table.sql — first migration, creates student table
• V2__create_course_table.sql — second migration, creates course table
• V3__add_department_to_student.sql— adds a FK column to student
• V4__seed_initial_data.sql — inserts reference data
• R__refresh_student_view.sql — repeatable migration (view definition)
5.2 Comprehensive Example — Entity Relationships + Flyway
Migration
// ── FILE: src/main/java/com/mits/model/[Link] ────────────────────
package [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
@Entity
@Table(name = "department")
@Data
public class Department {
@Id
@GeneratedValue(strategy = [Link])
private Long id;
@Column(nullable = false, unique = true, length = 100)
private String name;
// mappedBy = "department" tells JPA: "the foreign key lives in the student
table,
// in the column mapped by [Link] field."
// cascade = ALL: saving/deleting a department cascades to its students.
// orphanRemoval = true: if a student is removed from this list, delete it
from DB.
@OneToMany(mappedBy = "department", cascade = [Link],
orphanRemoval = true)
private List<Student> students = new ArrayList<>();
// Helper method to keep both sides of the bidirectional relationship in
sync.
Page 13 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
// Always use this instead of directly mutating [Link]().
public void addStudent(Student student) {
[Link](student);
[Link](this);
}
public void removeStudent(Student student) {
[Link](student);
[Link](null);
}
}
// ── FILE: src/main/java/com/mits/model/[Link] ────────────────────────
// Many-to-many: a student can enrol in many courses; a course has many
students.
package [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
@Entity
@Table(name = "course")
@Data
public class Course {
@Id
@GeneratedValue(strategy = [Link])
private Long id;
@Column(nullable = false, unique = true)
private String title;
// @ManyToMany requires a join table. Specify the join table and column
names
// explicitly so Hibernate does not generate cryptic names.
@ManyToMany
@JoinTable(
name = "student_course", // join table name
joinColumns = @JoinColumn(name = "course_id"), // FK to this entity
inverseJoinColumns = @JoinColumn(name = "student_id") // FK to the
other
)
private Set<Student> students = new HashSet<>(); // Set avoids
duplicates
}
// ── FILE: src/main/resources/db/migration/V1__initial_schema.sql ──────────
/*
Flyway runs this script ONCE and records it in flyway_schema_history.
All constraints are defined in SQL (NOT NULL, UNIQUE, FOREIGN KEY) —
Hibernate
annotations are for object mapping only; actual constraints belong in the DB.
*/
/*
CREATE TABLE department (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE student (
Page 14 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
status ENUM('ACTIVE','INACTIVE','GRADUATED') NOT NULL DEFAULT
'ACTIVE',
enrollment_date DATE,
department_id BIGINT,
CONSTRAINT fk_student_dept FOREIGN KEY (department_id) REFERENCES
department(id)
);
CREATE TABLE course (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL UNIQUE
);
CREATE TABLE student_course (
student_id BIGINT NOT NULL,
course_id BIGINT NOT NULL,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE
);
*/
5.3 [Link] — HikariCP Tuning
# ── Hikari connection pool settings ─────────────────────────────────────────
# Maximum connections kept open at a time.
# Rule of thumb: 10 * number of CPU cores, but never exceed DB max_connections.
[Link]-pool-size=10
# Minimum idle connections — kept warm to serve bursts of traffic.
[Link]-idle=5
# How long a connection can sit idle before being evicted (10 minutes).
[Link]-timeout=600000
# Maximum lifetime of a connection (30 minutes) — prevents stale connections.
[Link]-lifetime=1800000
# Flyway configuration
[Link]=true
[Link]=classpath:db/migration
[Link]-on-migrate=true # safe when applying Flyway to
existing DB
5.4 Common Mistakes
• Not setting ON DELETE CASCADE on foreign keys — deleting a parent row throws a
constraint violation.
• Using List instead of Set for @ManyToMany — causes duplicate join records and
HibernateException.
• Modifying an applied Flyway migration — Flyway checksums scripts and throws an error if
they change.
Page 15 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
• maximum-pool-size larger than MySQL max_connections — causes "Too many
connections" errors under load.
5.5 Practice Exercises
• Write V2__add_phone_to_student.sql that adds a nullable phone column to the student
table.
• Model a @ManyToMany between Student and Course and verify the join table is created
correctly.
• Configure Flyway to run migrations against an H2 in-memory database during tests.
Chapter 6: Spring Security & JWT
Spring Security is a powerful and highly customisable security framework. When you add
spring-boot-starter-security to the classpath, Spring Boot auto-configures a default security
chain that requires HTTP Basic authentication for every request. In a REST API you replace this
with a stateless JWT-based mechanism: the client sends credentials once (login endpoint),
receives a signed token, and includes that token in the Authorization header of every
subsequent request.
JSON Web Token (JWT) is an open standard (RFC 7519) for securely transmitting claims
between parties as a JSON object. A JWT consists of three Base64URL-encoded parts
separated by dots: Header (algorithm and token type), Payload (claims such as subject, roles,
expiry), and Signature (HMAC-SHA256 of [Link] using a secret key). The server signs
the token at login; on subsequent requests, the server verifies the signature to trust the claims
without touching the database.
The Spring Security filter chain is a sequence of Servlet filters that intercept every HTTP
request. You configure it in a @Configuration class that declares a SecurityFilterChain bean.
For JWT, you write a JwtAuthFilter that extends OncePerRequestFilter: it extracts the token
from the Authorization header, validates it, and sets an Authentication object in the
SecurityContextHolder so downstream code knows who is making the request.
Role-based access control (RBAC) restricts which endpoints a user can reach based on their
roles (ADMIN, STUDENT, TEACHER). In Spring Security, roles are granted authorities. You
can enforce them at the URL level in SecurityFilterChain (e.g.,
.requestMatchers("/api/admin/**").hasRole("ADMIN")) or at the method level using
@PreAuthorize("hasRole('ADMIN')") — which requires @EnableMethodSecurity on a
configuration class.
6.1 JWT Authentication Flow
• 1. Client POST /api/auth/login with { "email": "...", "password": "..." }
• 2. Server verifies credentials, signs a JWT with user ID + roles + expiry
• 3. Server returns { "token": "eyJ..." }
• 4. Client stores token in memory (NOT localStorage for sensitive apps)
• 5. Client includes Authorization: Bearer eyJ... in every subsequent request
• 6. JwtAuthFilter validates signature and expiry, sets SecurityContext
• 7. Controller/Service can call [Link]().getAuthentication()
Page 16 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
6.2 Comprehensive Example — SecurityConfig + JwtUtil +
JwtAuthFilter
// ── FILE: src/main/java/com/mits/security/[Link] ────────────────────
package [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].Base64;
import [Link];
@Component
public class JwtUtil {
// Inject the secret from [Link] — never hard-code it.
// The secret must be at least 256 bits (32 bytes) for HMAC-SHA256.
@Value("${[Link]}")
private String secret;
@Value("${[Link]-ms:86400000}") // default: 24 hours
private long expirationMs;
// Build a signing key from the Base64-encoded secret
private Key getSigningKey() {
byte[] keyBytes = [Link]().decode(secret);
return [Link](keyBytes);
}
// Generate a JWT after successful authentication.
// subject = email, claim "roles" = comma-separated role names
public String generateToken(String subject, String roles) {
return [Link]()
.setSubject(subject) // identifies the user
.claim("roles", roles) // custom claim
.setIssuedAt(new Date()) // iat claim
.setExpiration(new Date([Link]() + expirationMs))
// exp claim
.signWith(getSigningKey(), SignatureAlgorithm.HS256) // sign with
secret
.compact();
}
// Parse and validate the token. Returns the Claims (payload) or throws.
public Claims validateAndExtract(String token) {
return [Link]()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token) // throws ExpiredJwtException,
MalformedJwtException
.getBody();
}
}
// ── FILE: src/main/java/com/mits/security/[Link] ──────────────
package [Link];
Page 17 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import
[Link]
;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
// OncePerRequestFilter guarantees the filter runs exactly once per request,
// even in async dispatch scenarios.
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtAuthFilter(JwtUtil jwtUtil) { [Link] = jwtUtil; }
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws ServletException,
IOException {
String header = [Link]("Authorization");
// If no Bearer token is present, skip this filter.
// Spring Security will reject the request if the endpoint requires
auth.
if (header == null || ) {
[Link](req, res);
return;
}
String token = [Link](7); // strip "Bearer " prefix
try {
Claims claims = [Link](token);
String email = [Link]();
String roles = (String) [Link]("roles"); //
"ROLE_ADMIN,ROLE_STUDENT"
// Convert role strings to GrantedAuthority objects
List<SimpleGrantedAuthority> authorities =
[Link]([Link](","))
.map(SimpleGrantedAuthority::new)
.collect([Link]());
// Create an Authentication token and store it in the
SecurityContext.
// Spring Security uses this to authorise the request.
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(email, null,
authorities);
Page 18 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
[Link]().setAuthentication(authentication);
} catch (Exception e) {
// Invalid/expired token — clear context and let the request
proceed
// as unauthenticated (will be rejected by the security chain if
needed).
[Link]();
}
[Link](req, res); // continue to next filter / controller
}
}
6.3 Common Mistakes
• Storing JWTs in localStorage — vulnerable to XSS. Use HttpOnly cookies for sensitive
applications.
• Using a weak or default secret key — attackers can forge tokens. Use a 256-bit random
secret.
• Not setting an expiry on the token — a stolen token is valid forever.
• Forgetting to permit /api/auth/** in SecurityFilterChain — login endpoint requires no
authentication.
• Using hasRole("ADMIN") when the authority is stored as "ROLE_ADMIN" — Spring
prepends "ROLE_" automatically for hasRole but not hasAuthority.
6.4 Practice Exercises
• Add a refresh-token endpoint that issues a new JWT without requiring the password
again.
• Implement @PreAuthorize("hasRole('ADMIN')") on the DELETE /students endpoint and
test it.
• Write an integration test that calls /api/auth/login and uses the returned token to access a
protected endpoint.
Chapter 7: React Frontend
React is a declarative JavaScript library for building user interfaces. Its core idea is that the UI is
a function of state: UI = f(state). When state changes, React efficiently re-renders only the
affected parts of the DOM using a virtual DOM diffing algorithm. Modern React is written using
functional components — plain JavaScript functions that accept a props object and return JSX
(JavaScript XML, a syntax extension that looks like HTML).
Vite is the recommended build tool for new React projects. It uses ES modules in development
for near-instant server start and Hot Module Replacement (HMR), and Rollup for production
builds. Creating a project is straightforward: npm create vite@latest my-app -- --template react.
The development server starts on port 5173 and proxies API requests to the Spring Boot
backend to avoid CORS issues during development.
Page 19 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
React Hooks are functions that let you use state and other React features inside functional
components. useState returns a stateful value and a setter function; calling the setter schedules
a re-render with the new value. useEffect runs side effects (data fetching, subscriptions, DOM
manipulation) after the component renders. The dependency array controls when the effect re-
runs: empty [] means run once after mount; [id] means re-run whenever id changes; omitting the
array means run after every render.
Axios is a promise-based HTTP client that works in both [Link] and browsers. It automatically
serialises JavaScript objects to JSON for request bodies and parses JSON response bodies.
You should create a centralised Axios instance ([Link]) with the base URL and default
headers, and attach a request interceptor that adds the Authorization header from the stored
JWT. This avoids duplicating authentication logic across every API call.
7.1 Project Structure
• src/api/ — Axios instance and API helper functions
• src/components/ — Reusable UI components (Button, Modal, Table)
• src/pages/ — Route-level components (StudentList, StudentForm, Login)
• src/hooks/ — Custom hooks (useStudents, useAuth)
• src/context/ — React Context providers (AuthContext)
• src/utils/ — Pure utility functions
7.2 Comprehensive Example — Axios Instance + useStudents Hook +
StudentList
// ── FILE: src/api/[Link] ─────────────────────────────────────────
import axios from 'axios';
// VITE_API_URL is read from the .env file (VITE_ prefix required for Vite).
// In production, set this environment variable to your backend URL.
const api = [Link]({
baseURL: [Link].VITE_API_URL || '[Link]
headers: { 'Content-Type': 'application/json' },
timeout: 10000, // abort request after 10 seconds
});
// Request interceptor: attach JWT to every outgoing request.
// This runs before every request, so the token is always fresh.
[Link](
(config) => {
const token = [Link]('jwt'); // or from Context/Redux
if (token) {
[Link] = `Bearer ${token}`;
}
return config;
},
(error) => [Link](error)
);
// Response interceptor: handle 401 globally (token expired → redirect to
login).
[Link](
(response) => response, // pass through successful responses
(error) => {
if ([Link]?.status === 401) {
[Link]('jwt');
Page 20 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
[Link] = '/login'; // force re-authentication
}
return [Link](error);
}
);
export default api;
// ── FILE: src/hooks/[Link] ─────────────────────────────────────────
import { useState, useEffect, useCallback } from 'react';
import api from '../api/axiosInstance';
// Custom hook: encapsulates ALL student data logic.
// Components that need students import this hook — they do not care about
// how data is fetched or which endpoint is called.
export function useStudents() {
const [students, setStudents] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// useCallback memoises fetchStudents so it does not change on every render,
// preventing an infinite useEffect loop when passed as a dependency.
const fetchStudents = useCallback(async () => {
setLoading(true);
setError(null);
try {
const { data } = await [Link]('/students');
setStudents(data);
} catch (err) {
setError([Link]?.data?.message || 'Failed to load students');
} finally {
setLoading(false); // always clear loading, even on error
}
}, []);
// Run once when the component mounts (empty dependency array).
useEffect(() => { fetchStudents(); }, [fetchStudents]);
const deleteStudent = async (id) => {
await [Link](`/students/${id}`);
// Optimistic update: remove from local state immediately without
refetching
setStudents((prev) => [Link]((s) => [Link] !== id));
};
return { students, loading, error, refetch: fetchStudents, deleteStudent };
}
// ── FILE: src/pages/[Link] ────────────────────────────────────────
import React from 'react';
import { useStudents } from '../hooks/useStudents';
import { Link } from 'react-router-dom';
export default function StudentList() {
const { students, loading, error, deleteStudent } = useStudents();
if (loading) return <p>Loading students...</p>;
if (error) return <p style={{ color: 'red' }}>Error: {error}</p>;
return (
<div>
Page 21 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
<h2>Students</h2>
<Link to="/students/new">+ Add Student</Link>
<table>
<thead>
<tr><th>Name</th><th>Email</th><th>Status</th><th>Actions</th></tr>
</thead>
<tbody>
{[Link]((s) => (
<tr key={[Link]}>
<td>{[Link]}</td>
<td>{[Link]}</td>
<td>{[Link]}</td>
<td>
<Link to={`/students/${[Link]}/edit`}>Edit</Link>
<button onClick={() => deleteStudent([Link])}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
7.3 Common Mistakes
• Calling an API inside useEffect without a dependency array — runs on every render
causing infinite loops.
• Mutating state directly ([Link](newStudent)) — React will not detect the change
and re-render.
• Storing the JWT in localStorage — vulnerable to XSS. Use HttpOnly cookies for high-
security apps.
• Not handling the error and loading states — shows a blank screen while data is fetching.
• Missing the key prop on list items — React cannot efficiently update the DOM.
7.4 Practice Exercises
• Add a search input to StudentList that filters the displayed list client-side.
• Create a useStudent(id) hook that fetches a single student and use it in an edit form.
• Add a loading spinner component that renders while the API call is in flight.
Chapter 8: React Router & State Management
React Router v6 brings a declarative, component-based routing model. You wrap your
application in <BrowserRouter>, define routes with <Routes> and <Route>, and link between
pages with <Link> or <NavLink>. The key improvement in v6 over v5 is that routes are matched
by best-match rather than first-match, eliminating the need for the exact prop. Nested routes are
expressed by nesting <Route> elements and rendering an <Outlet /> in the parent.
Programmatic navigation — redirecting after a form submission or login — is handled by the
useNavigate hook. useParams extracts dynamic segments from the URL (e.g., the :id from
Page 22 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
/students/:id). useLocation gives you the current location object, useful for reading query
parameters or redirecting back to the originally requested page after login.
React Context API provides a way to share state across the component tree without manually
passing props at every level (prop drilling). You create a Context with [Link](),
provide a value at a high level in the tree with <[Link] value={...}>, and consume it
anywhere below with useContext(). It is ideal for global state that changes infrequently —
authentication state, theme, language preferences.
Redux Toolkit (RTK) is the modern, opinionated way to use Redux. It eliminates the boilerplate
of classic Redux by providing createSlice() (combines actions and reducers), configureStore()
(sets up the store with good defaults including Redux DevTools), and createAsyncThunk() for
data fetching. Use RTK when your application has complex state that is shared and mutated by
many disconnected components — a shopping cart, a notification system, real-time data.
8.1 When to Use Which State Solution
• useState — Local component state (form inputs, toggle, modal open/close)
• useReducer — Complex local state with multiple sub-values
• Context API — Global state that changes infrequently (auth user, theme)
• Redux Toolkit — Complex shared state, many updates, time-travel debugging
• React Query/SWR — Server state (caching, background refresh, pagination)
8.2 Comprehensive Example — Router + AuthContext + Protected
Route
// ── FILE: src/context/[Link] ──────────────────────────────────────
import React, { createContext, useContext, useState, useEffect } from 'react';
import api from '../api/axiosInstance';
// Create the context with a default value of null.
// Components that call useAuth() outside of AuthProvider will get null.
const AuthContext = createContext(null);
// AuthProvider wraps the entire app and makes auth state available everywhere.
export function AuthProvider({ children }) {
const [user, setUser] = useState(null); // { email, roles }
const [loading, setLoading] = useState(true); // true while checking stored
token
// On mount: check if a valid token exists in localStorage.
// This restores the session after a page refresh.
useEffect(() => {
const token = [Link]('jwt');
if (token) {
// Decode the payload (middle part) without verifying signature
// (server validates the full token on every API call)
try {
const payload = [Link](atob([Link]('.')[1]));
if ([Link] * 1000 > [Link]()) { // check expiry
setUser({ email: [Link], roles: [Link]?.split(',') });
} else {
[Link]('jwt'); // expired — clear it
}
} catch { [Link]('jwt'); }
}
setLoading(false);
Page 23 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
}, []);
const login = async (email, password) => {
const { data } = await [Link]('/auth/login', { email, password });
[Link]('jwt', [Link]);
const payload = [Link](atob([Link]('.')[1]));
setUser({ email: [Link], roles: [Link]?.split(',') });
};
const logout = () => {
[Link]('jwt');
setUser(null);
};
return (
<[Link] value={{ user, loading, login, logout }}>
{children}
</[Link]>
);
}
// Custom hook — components call useAuth() instead of useContext(AuthContext)
export const useAuth = () => useContext(AuthContext);
// ── FILE: src/components/[Link] ────────────────────────────────
import React from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
// ProtectedRoute wraps a route element. If the user is not authenticated,
// it redirects to /login and stores the attempted URL so we can redirect back.
export default function ProtectedRoute({ children, requiredRole }) {
const { user, loading } = useAuth();
const location = useLocation();
if (loading) return <p>Checking authentication...</p>;
if (!user) {
// [Link] allows the login page to redirect back after successful login
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (requiredRole && ![Link]?.includes(requiredRole)) {
return <Navigate to="/403" replace />;
}
return children;
}
// ── FILE: src/[Link] ──────────────────────────────────────────────────────
import React from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
import Login from './pages/Login';
import StudentList from './pages/StudentList';
import StudentForm from './pages/StudentForm';
import AdminDashboard from './pages/AdminDashboard';
export default function App() {
return (
Page 24 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
<AuthProvider> {/* provides auth state to entire tree */}
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
{/* Any authenticated user can access these */}
<Route path="/students" element={
<ProtectedRoute><StudentList /></ProtectedRoute>
} />
<Route path="/students/:id/edit" element={
<ProtectedRoute><StudentForm /></ProtectedRoute>
} />
{/* Only ROLE_ADMIN can access this */}
<Route path="/admin" element={
<ProtectedRoute requiredRole="ROLE_ADMIN"><AdminDashboard
/></ProtectedRoute>
} />
{/* Default redirect */}
<Route path="/" element={<Navigate to="/students" replace />} />
</Routes>
</BrowserRouter>
</AuthProvider>
);
}
8.3 Common Mistakes
• Nesting Routes without <Outlet /> in the parent component — child routes render nothing.
• Using useNavigate() at the top level outside BrowserRouter — throws a hook context
error.
• Putting rapidly changing state (e.g., form input values) in Context — causes the entire tree
to re-render on every keystroke.
• Not handling the loading state in ProtectedRoute — shows a flash of the redirect before
the stored token is checked.
8.4 Practice Exercises
• Add a NavBar component that shows the logged-in user's email and a Logout button using
useAuth().
• Implement a /students/new route that reuses StudentForm in "create" mode.
• Add a Redux Toolkit slice for notifications (success/error toasts) shared across pages.
Chapter 9: Forms, Validation & Error Handling
Forms are a critical part of any data-driven application. In React, forms can be controlled (React
state drives the input value) or uncontrolled (the DOM manages the value via refs). React Hook
Form takes a hybrid approach: it uses refs by default for performance (avoiding re-renders on
every keystroke) but provides a clean API for accessing and validating values. For complex
forms with dozens of fields, this performance difference is significant.
Page 25 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
Yup is a JavaScript schema-builder for value parsing and validation. You define a schema that
describes the shape and constraints of your form data, then pass it to React Hook Form via the
yupResolver. When the form is submitted, Yup validates all fields simultaneously and React
Hook Form populates the errors object with any failures — which you can then display next to
the relevant input.
On the backend, Spring Boot's Bean Validation (JSR-380) provides a symmetric validation
layer. Annotating a DTO with @NotBlank, @Email, @Size, @Min, and so on, and placing
@Valid before the @RequestBody parameter in the controller, triggers validation before the
method body executes. If validation fails, Spring throws a MethodArgumentNotValidException.
A centralised @RestControllerAdvice exception handler converts this (and other exceptions)
into a consistent JSON error response.
A consistent error response format is essential for client-side error handling. Define an
ErrorResponseDTO with fields like timestamp, status, message, and errors (a map of field name
to error message). Your @RestControllerAdvice maps each exception type to the appropriate
HTTP status and this DTO. The React frontend then knows exactly where to find the error
message, regardless of which endpoint was called.
9.1 Backend Validation Annotations Reference
• @NotBlank — string must not be null and must contain at least one non-whitespace
character
• @NotNull — field must not be null (works for any type)
• @Email — string must match email format
• @Size(min, max) — string/collection length must be within bounds
• @Min(value) — numeric value must be >= min
• @Max(value) — numeric value must be <= max
• @Pattern(regexp)— string must match the given regular expression
• @Past — date must be in the past
• @Future — date must be in the future
9.2 Comprehensive Example — React Hook Form + Yup + Spring
GlobalExceptionHandler
// ── FILE: src/pages/[Link] ────────────────────────────────────────
import React from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { useNavigate, useParams } from 'react-router-dom';
import api from '../api/axiosInstance';
// Yup schema — single source of truth for validation rules.
// Define it OUTSIDE the component to prevent recreation on every render.
const studentSchema = [Link]({
name: [Link]()
.required('Name is required')
.min(2, 'Name must be at least 2 characters')
.max(100, 'Name cannot exceed 100 characters'),
email: [Link]()
.required('Email is required')
.email('Must be a valid email address'),
status: [Link]()
Page 26 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
.oneOf(['ACTIVE', 'INACTIVE', 'GRADUATED'], 'Invalid status')
.required('Status is required'),
});
export default function StudentForm() {
const navigate = useNavigate();
const { id } = useParams(); // present when editing, absent when creating
const isEdit = Boolean(id);
// useForm returns register, handleSubmit, formState, setError, reset
const {
register,
handleSubmit,
setError, // programmatically set a field error (e.g., server-side)
formState: { errors, isSubmitting },
} = useForm({
resolver: yupResolver(studentSchema),
defaultValues: { status: 'ACTIVE' },
});
const onSubmit = async (data) => {
try {
if (isEdit) {
await [Link](`/students/${id}`, data);
} else {
await [Link]('/students', data);
}
navigate('/students'); // redirect on success
} catch (err) {
const serverErrors = [Link]?.data?.errors;
if (serverErrors) {
// Map backend field errors to React Hook Form field errors
// e.g. { "email": "Email already in use" }
[Link](serverErrors).forEach(([field, msg]) =>
setError(field, { type: 'server', message: msg })
);
}
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Name</label>
<input {...register('name')} />
{[Link] && <span style={{ color: 'red'
}}>{[Link]}</span>}
</div>
<div>
<label>Email</label>
<input type="email" {...register('email')} />
{[Link] && <span style={{ color:
'red' }}>{[Link]}</span>}
</div>
<div>
<label>Status</label>
<select {...register('status')}>
<option value="ACTIVE">Active</option>
<option value="INACTIVE">Inactive</option>
<option value="GRADUATED">Graduated</option>
</select>
Page 27 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
{[Link] && <span style={{ color:
'red' }}>{[Link]}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Saving...' : isEdit ? 'Update' : 'Create'}
</button>
</form>
);
}
// ── FILE: src/main/java/com/mits/dto/[Link] ───────────────────────
// Data Transfer Object — decouples API contract from internal entity.
package [Link];
import [Link].*;
public record StudentDTO(
Long id, // null for create
requests
@NotBlank(message = "Name is required")
@Size(min = 2, max = 100)
String name,
@NotBlank @Email(message = "Must be a valid email")
String email,
@NotBlank
String status
) {}
// ── FILE: src/main/java/com/mits/exception/[Link] ─────
package [Link];
import [Link].*;
import [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
// @RestControllerAdvice = @ControllerAdvice + @ResponseBody
// One class handles ALL exceptions for ALL controllers — no try/catch needed
in controllers.
@RestControllerAdvice
public class GlobalExceptionHandler {
// Handles Bean Validation failures (@Valid on @RequestBody)
@ExceptionHandler([Link])
public ResponseEntity<Map<String, Object>> handleValidation(
MethodArgumentNotValidException ex) {
// Collect field-level errors into { "fieldName": "error message" }
Map<String, String> fieldErrors =
[Link]().getFieldErrors().stream()
.collect([Link](
FieldError::getField,
FieldError::getDefaultMessage,
(a, b) -> a // keep first message if same field has multiple
errors
));
return [Link]().body([Link](
"timestamp", [Link]().toString(),
"status", 400,
Page 28 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
"message", "Validation failed",
"errors", fieldErrors
));
}
// Handles custom ResourceNotFoundException (thrown by service when entity
not found)
@ExceptionHandler([Link])
public ResponseEntity<Map<String, Object>>
handleNotFound(ResourceNotFoundException ex) {
return [Link](HttpStatus.NOT_FOUND).body([Link](
"timestamp", [Link]().toString(),
"status", 404,
"message", [Link]()
));
}
}
9.3 Common Mistakes
• Validating only on the frontend — a determined user can bypass browser validation with
curl.
• Returning a 200 with an "error" field in the body — clients cannot distinguish success from
failure.
• Catching every exception with a generic 500 handler — clients cannot programmatically
handle specific errors.
• Not escaping user input before displaying it in the DOM — opens XSS vulnerabilities.
9.4 Practice Exercises
• Add a @Pattern constraint to the phone field in StudentDTO that validates a 10-digit
number.
• Extend GlobalExceptionHandler to return 409 Conflict when a
DataIntegrityViolationException is caught.
• Implement a useFormWithServer hook that wraps React Hook Form and automatically
maps server errors.
Chapter 10: File Upload, Email & Scheduling
File upload is a common requirement in educational portals — uploading profile pictures,
assignment submissions, or mark sheets. Spring Boot handles multipart file uploads through the
MultipartFile interface. You configure the maximum file size in [Link] and write a
controller method that accepts MultipartFile. The file can be stored on the local filesystem
(simple but not scalable), in a cloud object store like AWS S3 (scalable), or in the database as a
BLOB (simple for small files but degrades DB performance).
Spring Boot provides a JavaMailSender abstraction that works with any SMTP server — Gmail,
SendGrid, AWS SES, or a local mail server. You configure the SMTP host, port, credentials,
and TLS settings in [Link]. For templated emails (HTML with personalised
content), use Thymeleaf templates. In development, Mailhog or Mailtrap provide a local SMTP
server that captures emails without actually sending them, so you can inspect them in a web UI.
Page 29 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
@Scheduled turns any Spring bean method into a cron job. You annotate the method with
@Scheduled(cron = "0 0 8 * * ?") and enable scheduling with @EnableScheduling on a
configuration class. The cron expression follows the standard Unix format with an additional
seconds field. Common use cases: sending daily attendance reports, archiving old data,
sending reminder emails, refreshing cached data from an external API.
@Async enables asynchronous method execution. When an @Async method is called, Spring
submits the method to a thread pool executor and returns immediately to the caller. The return
type should be void or CompletableFuture<T>. This is ideal for fire-and-forget operations like
sending emails or triggering notifications — you do not want the HTTP response to wait for an
email to be sent. You must add @EnableAsync to a configuration class to activate this feature.
10.1 Comprehensive Example — File Upload + Email + Scheduling
// ── FILE: src/main/java/com/mits/service/[Link] ───────────
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
@Service
public class FileStorageService {
// Inject the upload directory from [Link].
// This avoids hard-coding a path and makes it configurable per
environment.
@Value("${[Link]-dir:uploads}")
private String uploadDir;
public String store(MultipartFile file) throws IOException {
// Validate file type — never trust the content-type header alone.
// A malicious user can upload a .sh file named [Link].
String originalName = [Link]();
if (originalName == null || !isImageFile(originalName)) {
throw new IllegalArgumentException("Only JPG, PNG, GIF files are
allowed");
}
// Generate a unique filename to avoid collisions and path traversal
attacks.
// NEVER use the original filename directly — it could contain
"../../../etc/passwd"
String extension =
[Link]([Link]('.'));
String storedName = [Link]().toString() + extension;
// Ensure the upload directory exists
Path uploadPath = [Link](uploadDir);
[Link](uploadPath);
// Copy the uploaded bytes to the target file
Path targetPath = [Link](storedName);
[Link]([Link](), targetPath,
StandardCopyOption.REPLACE_EXISTING);
Page 30 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
return storedName; // return the stored filename for saving in the
database
}
private boolean isImageFile(String filename) {
String lower = [Link]();
return [Link](".jpg") || [Link](".jpeg")
|| [Link](".png") || [Link](".gif");
}
}
// ── FILE: src/main/java/com/mits/service/[Link] ────────────────
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Service
public class EmailService {
private final JavaMailSender mailSender;
public EmailService(JavaMailSender mailSender) {
[Link] = mailSender;
}
// @Async: Spring submits this method to a background thread pool.
// The caller does NOT wait for the email to be sent — HTTP response
// returns immediately after the method is called.
// IMPORTANT: @Async only works when called from OUTSIDE this class
// (Spring wraps the bean in a proxy; self-calls bypass the proxy).
@Async
public void sendWelcomeEmail(String toEmail, String studentName) {
try {
MimeMessage message = [Link]();
MimeMessageHelper helper = new MimeMessageHelper(message, true,
"UTF-8");
[Link]("noreply@[Link]");
[Link](toEmail);
[Link]("Welcome to MITS Student Portal");
// HTML email body — in production use a Thymeleaf template
String html = "<h2>Welcome, " + studentName + "!</h2>"
+ "<p>Your account has been created successfully.</p>"
+ "<p>Please log in at <a
href='[Link]
[Link](html, true); // true = isHtml
[Link](message);
} catch (MessagingException e) {
// Log but do not rethrow — a failed email should not fail the
registration
[Link]("Failed to send email to " + toEmail + ": " +
[Link]());
Page 31 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
}
}
}
// ── FILE: src/main/java/com/mits/scheduler/[Link] ───────────
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Component
public class ReportScheduler {
private final StudentRepository studentRepository;
private final EmailService emailService;
public ReportScheduler(StudentRepository studentRepository, EmailService
emailService) {
[Link] = studentRepository;
[Link] = emailService;
}
// Cron expression: "second minute hour day-of-month month day-of-week"
// "0 0 8 * * MON-FRI" = every weekday at 08:00:00
@Scheduled(cron = "0 0 8 * * MON-FRI")
public void sendDailyAttendanceReminder() {
long activeCount = [Link](
[Link]).size();
[Link]("[Scheduler] " + [Link]()
+ " — " + activeCount + " active students. Sending reminders...");
// In a real application: query students with missing attendance and
email them
}
// fixedDelay: wait 60 seconds AFTER the previous run completes before
running again.
// Avoids overlap if the task takes longer than the interval.
@Scheduled(fixedDelay = 60_000)
public void healthCheck() {
[Link]("[Health] Application running at " +
[Link]());
}
}
10.2 Common Mistakes
• Storing uploaded files inside the JAR or WAR — they disappear on redeploy. Use an
external directory or S3.
• Using the original filename for storage — path traversal attack vector.
• Calling an @Async method from within the same class — Spring cannot intercept self-
calls, so @Async is ignored.
• Not adding @EnableAsync and @EnableScheduling — the annotations on methods are
silently ignored.
Page 32 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
• Sending emails synchronously in the request thread — a slow SMTP server makes the
HTTP response hang.
10.3 Practice Exercises
• Extend FileStorageService to support PDF uploads and serve them via a /files/{filename}
endpoint.
• Send a welcome email asynchronously when a new student is registered.
• Write a scheduler that runs every Sunday at midnight to archive inactive students.
Chapter 11: Deployment
Deploying a full-stack Java application to a Linux VPS (Virtual Private Server) is a fundamental
skill. The Spring Boot backend is packaged as a self-contained executable JAR using mvn
clean package -DskipTests. The React frontend is built into a dist/ folder of static files using npm
run build. Nginx acts as a reverse proxy: it serves the React static files directly (fast) and
forwards /api/* requests to the Spring Boot process running on port 8080.
On the server, Spring Boot runs as a background process managed by systemd — the Linux
service manager. A systemd unit file defines the service name, the command to start it (java -jar
[Link]), environment variables, the working directory, and the restart policy. With systemd, your
application automatically starts on server reboot and restarts if it crashes, providing basic high
availability without additional tools.
For the React frontend, PM2 is a popular [Link] process manager, but for a static site built by
Vite, Nginx is all you need — there is no [Link] process to manage at runtime. You simply
copy the dist/ folder to /var/www/your-app and configure Nginx to serve it. For Single Page
Applications (SPAs), you must configure Nginx to redirect all routes to [Link] so that React
Router handles client-side routing correctly.
SSL/TLS certificates are essential for any public-facing application. Certbot, from the Let's
Encrypt project, provides free, auto-renewing TLS certificates. You install Certbot on the server,
run certbot --nginx -d [Link], and it automatically obtains a certificate and updates
your Nginx configuration. Certificates expire every 90 days but Certbot installs a cron job to
renew them automatically.
11.1 Deployment Checklist
• [ ] Set SPRING_PROFILES_ACTIVE=prod on the server
• [ ] Externalize DB credentials as environment variables (never commit to Git)
• [ ] Set ddl-auto=validate in prod (schema managed by Flyway)
• [ ] Configure Nginx to proxy /api/* to localhost:8080
• [ ] Serve React dist/ from Nginx with try_files $uri /[Link]
• [ ] Obtain SSL certificate with Certbot and configure HTTPS redirect
• [ ] Set up systemd service for Spring Boot with Restart=on-failure
• [ ] Configure ufw firewall: allow ports 22 (SSH), 80 (HTTP), 443 (HTTPS) only
Page 33 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
11.2 Comprehensive Example — systemd Unit + Nginx Config +
Deploy Script
# ── FILE: /etc/systemd/system/[Link] ─────────────────────
# systemd unit file for the Spring Boot application.
# Place this file on the server, then run:
# sudo systemctl daemon-reload
# sudo systemctl enable student-portal (start on boot)
# sudo systemctl start student-portal
# sudo systemctl status student-portal (check it is running)
[Unit]
Description=MITS Student Portal — Spring Boot Application
# Start after network and MySQL are available
After=[Link] [Link]
[Service]
Type=simple
User=deploy # run as a non-root user for security
WorkingDirectory=/opt/student-portal
# Environment variables — never put passwords in [Link] or
[Link]
Environment="SPRING_PROFILES_ACTIVE=prod"
Environment="DB_HOST=localhost"
Environment="DB_NAME=fullstack_db"
Environment="DB_USER=portal_user"
EnvironmentFile=/opt/student-portal/.env # load secrets from a file not in Git
# JVM flags: -Xms256m minimum heap, -Xmx512m maximum heap
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/student-portal/student-
[Link]
# Restart the service automatically if it crashes
Restart=on-failure
RestartSec=10
[Install]
WantedBy=[Link]
# ── FILE: /etc/nginx/sites-available/student-portal ──────────────────────
# Nginx acts as a reverse proxy: serves React files + forwards /api to Spring
Boot.
# After editing, run: sudo nginx -t && sudo systemctl reload nginx
server {
listen 80;
server_name [Link] [Link];
# Redirect all HTTP to HTTPS (Certbot adds this block automatically)
return 301 [Link]
}
server {
listen 443 ssl;
server_name [Link] [Link];
# Certbot will populate these paths automatically
ssl_certificate /etc/letsencrypt/live/[Link]/[Link];
ssl_certificate_key /etc/letsencrypt/live/[Link]/[Link];
Page 34 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
# ── React Frontend (static files from Vite build) ─────────────────────
root /var/www/student-portal;
index [Link];
# SPA routing: if the requested file does not exist, serve [Link]
# so React Router handles the URL client-side.
location / {
try_files $uri $uri/ /[Link];
}
# ── Spring Boot API (reverse proxy) ──────────────────────────────────
# All requests to /api/* are forwarded to the Spring Boot process.
location /api/ {
proxy_pass [Link] # Spring Boot listens on
8080
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # tells Spring the
original protocol
proxy_read_timeout 60s;
proxy_connect_timeout 5s;
}
# ── File uploads (served directly, with auth handled by Spring Boot) ──
location /files/ {
alias /opt/student-portal/uploads/;
# Add auth_request to require JWT validation before serving files
# auth_request /api/v1/auth/validate;
}
}
# ── FILE: [Link] ────────────────────────────────────────────────────────
#!/usr/bin/env bash
# Deployment script — run from local machine or CI/CD pipeline.
# Usage: ./[Link]
set -e # exit immediately if any command fails
SERVER="deploy@[Link]"
DEPLOY_DIR="/opt/student-portal"
JAR_NAME="[Link]"
echo "==> Building Spring Boot JAR..."
cd backend
mvn clean package -DskipTests -q # -q = quiet output
cd ..
echo "==> Building React frontend..."
cd frontend
npm ci # clean install (uses [Link]
exactly)
npm run build
cd ..
echo "==> Uploading JAR to server..."
scp backend/target/*.jar ${SERVER}:${DEPLOY_DIR}/${JAR_NAME}
echo "==> Uploading React build to server..."
rsync -az --delete frontend/dist/ ${SERVER}:/var/www/student-portal/
Page 35 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
echo "==> Restarting Spring Boot service..."
ssh ${SERVER} "sudo systemctl restart student-portal && sudo systemctl status
student-portal --no-pager"
echo "==> Deployment complete!"
11.3 Common Mistakes
• Running the application as root — a security vulnerability. Use a dedicated deploy user.
• Forgetting try_files $uri /[Link] in Nginx — refreshing any React Router page returns
404.
• Not setting proxy_set_header X-Forwarded-Proto — Spring Boot generates http:// URLs in
redirects.
• Opening port 8080 in the firewall — the API should only be accessible through Nginx on
443.
• Committing the .env file to Git — exposes database passwords. Add it to .gitignore.
11.4 Practice Exercises
• Write a GitHub Actions workflow that builds the JAR and runs [Link] on push to main.
• Configure Nginx to rate-limit the /api/auth/login endpoint to 10 requests per minute.
• Set up a MySQL backup cron job using mysqldump and upload the dump to S3.
Chapter 12: Capstone — Complete Student Portal
This capstone project brings together every concept from the previous eleven chapters into a
production-ready Student Portal. The system manages students, courses, results, and
attendance. The backend is a Spring Boot REST API secured with JWT. The frontend is a
React dashboard with interactive charts powered by [Link]. The database is MySQL with
schema managed by Flyway. The entire application is deployed on an Ubuntu VPS with Nginx
and HTTPS.
The architecture follows domain-driven design principles. The domain is divided into bounded
contexts: Student Management (CRUD, profiles, photo upload), Academic Management
(courses, enrolment, results), and Attendance (daily records, statistics, reports). Each bounded
context has its own controllers, services, repositories, and DTOs. This structure scales well:
different team members can work on different contexts without conflicts.
The React dashboard provides role-specific views: students see their own results, attendance,
and enrolled courses; teachers can enter results and mark attendance for their courses;
administrators have a full overview with analytics. [Link] renders bar charts for result
distributions, line charts for attendance trends, and doughnut charts for course enrolment
breakdowns. All charts are fed by dedicated /api/v1/dashboard/* endpoints that aggregate data
server-side.
The capstone intentionally exercises the full deployment pipeline: Flyway migrations run
automatically on startup, the Spring Boot JAR is deployed via the [Link] script from Chapter
11, the React build is served by Nginx, SSL is handled by Certbot. By building and deploying
this complete system, you gain confidence in the entire Java full-stack development workflow —
from requirements to production.
Page 36 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
12.1 Domain Model
• Student — id, name, email, phone, photo, status, department
• Course — id, title, code, credits, teacher (FK)
• Enrolment — id, student (FK), course (FK), enrolledAt
• Result — id, student (FK), course (FK), marks, grade, semester
• Attendance — id, student (FK), course (FK), date, status (PRESENT/ABSENT/LATE)
• User — id, email, passwordHash, roles (linked to Student or Teacher)
12.2 API Endpoints
• POST /api/v1/auth/register — create account, returns JWT
• POST /api/v1/auth/login — authenticate, returns JWT
• GET /api/v1/students — list students (ADMIN)
• GET /api/v1/students/{id}/results — get results for a student
• GET /api/v1/students/{id}/attendance — get attendance summary
• POST /api/v1/results — enter a result (TEACHER, ADMIN)
• POST /api/v1/attendance — mark attendance (TEACHER, ADMIN)
• GET /api/v1/dashboard/stats — summary counts for admin dashboard
• GET /api/v1/dashboard/attendance-trend — attendance % over last 30 days
• GET /api/v1/dashboard/result-distribution — marks histogram for a course
12.3 Comprehensive Example — Dashboard API + React Chart
// ── FILE: src/main/java/com/mits/controller/[Link] ───────
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
@RestController
@RequestMapping("/api/v1/dashboard")
public class DashboardController {
private final DashboardService dashboardService;
public DashboardController(DashboardService dashboardService) {
[Link] = dashboardService;
}
// Only ADMIN and TEACHER can see the dashboard statistics.
@PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
@GetMapping("/stats")
public ResponseEntity<DashboardStatsDTO> getStats() {
return [Link]([Link]());
}
Page 37 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
// Attendance trend: list of { date, attendancePercentage } for the last N
days.
@PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
@GetMapping("/attendance-trend")
public ResponseEntity<List<AttendanceTrendDTO>> getAttendanceTrend(
@RequestParam(defaultValue = "30") int days) {
return [Link]([Link](days));
}
}
// ── FILE: src/main/java/com/mits/service/[Link] ─────────────
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Service
public class DashboardService {
private final StudentRepository studentRepo;
private final CourseRepository courseRepo;
private final AttendanceRepository attendanceRepo;
private final ResultRepository resultRepo;
public DashboardService(StudentRepository s, CourseRepository c,
AttendanceRepository a, ResultRepository r) {
[Link] = s;
[Link] = c;
[Link] = a;
[Link] = r;
}
public DashboardStatsDTO getStats() {
return new DashboardStatsDTO(
[Link](), // total students
[Link](), // total courses
[Link](), // total results entered
[Link]([Link]()) // today's attendance
records
);
}
public List<AttendanceTrendDTO> getAttendanceTrend(int days) {
LocalDate from = [Link]().minusDays(days);
// Repository query returns List<Object[]> { date, presentCount,
totalCount }
return [Link](from).stream()
.map(row -> new AttendanceTrendDTO(
(LocalDate) row[0],
((Long) row[1] * 100.0) / (Long) row[2] // present / total *
100
))
Page 38 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
.collect([Link]());
}
}
// ── FILE: src/pages/[Link] ──────────────────────────────────────────
import React, { useEffect, useState } from 'react';
import { Line, Bar, Doughnut } from 'react-chartjs-2';
import {
Chart as ChartJS, CategoryScale, LinearScale,
PointElement, LineElement, BarElement, ArcElement,
Title, Tooltip, Legend
} from '[Link]';
import api from '../api/axiosInstance';
// Register [Link] components — required for tree-shaking
[Link](CategoryScale, LinearScale, PointElement,
LineElement, BarElement, ArcElement, Title, Tooltip, Legend);
export default function Dashboard() {
const [stats, setStats] = useState(null);
const [trend, setTrend] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch both endpoints concurrently with [Link]
[Link]([
[Link]('/dashboard/stats'),
[Link]('/dashboard/attendance-trend?days=30'),
]).then(([statsRes, trendRes]) => {
setStats([Link]);
setTrend([Link]);
}).finally(() => setLoading(false));
}, []);
if (loading) return <p>Loading dashboard...</p>;
// Prepare [Link] data for the attendance line chart
const attendanceChartData = {
labels: [Link](d => [Link]),
datasets: [{
label: 'Attendance %',
data: [Link](d => [Link](1)),
borderColor: 'rgb(59, 130, 246)', // Tailwind blue-500
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4, // smooth curve
fill: true,
}],
};
const chartOptions = {
responsive: true,
plugins: {
legend: { position: 'top' },
title: { display: true, text: 'Attendance Trend — Last 30 Days' },
},
scales: {
y: { min: 0, max: 100, ticks: { callback: v => v + '%' } },
},
};
return (
Page 39 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
<div style={{ padding: '1rem' }}>
<h2>Admin Dashboard</h2>
{/* Summary Cards */}
{stats && (
<div style={{ display: 'flex', gap: '1rem', marginBottom: '2rem' }}>
{[
{ label: 'Students', value: [Link] },
{ label: 'Courses', value: [Link] },
{ label: 'Results', value: [Link] },
{ label: 'Today's Attendance', value: [Link] },
].map(card => (
<div key={[Link]} style={{
flex: 1, padding: '1rem', background: '#f0f9ff',
borderRadius: '8px', textAlign: 'center'
}}>
<div style={{ fontSize: '2rem', fontWeight:
'bold' }}>{[Link]}</div>
<div style={{ color: '#6b7280' }}>{[Link]}</div>
</div>
))}
</div>
)}
{/* Attendance Trend Line Chart */}
<div style={{ maxWidth: '800px' }}>
<Line data={attendanceChartData} options={chartOptions} />
</div>
</div>
);
}
12.4 Capstone Implementation Steps
• Step 1 : Set up Spring Boot project with all dependencies (Ch2)
• Step 2 : Write Flyway V1 migration for all tables (Ch5)
• Step 3 : Create entities — Student, Course, Enrolment, Result, Attendance (Ch4)
• Step 4 : Implement CRUD REST controllers with DTOs and validation (Ch3, Ch9)
• Step 5 : Add Spring Security with JWT login/register (Ch6)
• Step 6 : Add file upload for student photos and welcome email on registration (Ch10)
• Step 7 : Implement dashboard aggregation endpoints (Ch3)
• Step 8 : Scaffold React app with Vite, configure Axios instance (Ch7)
• Step 9 : Build AuthContext, ProtectedRoute, React Router setup (Ch8)
• Step 10: Build StudentList, StudentForm with React Hook Form + Yup (Ch9)
• Step 11: Build Dashboard page with [Link] line and bar charts (Ch12)
• Step 12: Deploy with [Link], Nginx, systemd, Certbot (Ch11)
12.5 Final Practice Challenge
• Add a PDF export of a student's mark sheet using Apache PDFBox or iText.
• Implement real-time attendance notifications using Spring WebSocket (STOMP) and
React.
• Add a dark mode toggle to the React dashboard using Context API and CSS variables.
• Write end-to-end tests for the login and result-entry flows using Playwright.
Page 40 | MITS Academy | [Link]
MITS Academy — Java Full Stack Development
• Deploy the application using Docker Compose: mysql, spring-boot, and nginx containers.
Page 41 | MITS Academy | [Link]