Projects
2026
Three Years Later: Rebuilding My College Capstone

Three Years Later: Rebuilding My College Capstone

From PHP/vanilla JS to Nuxt 4/TypeScript—what I learned by recreating my own work

Nuxt.jsTypeScriptPiniaTailwind CSSExpress.jsMySQL

THE BACKSTORY

In 2023, I built a Commuter Security Management System for my college capstone. It was a complete platform:

  • Mobile app for passengers to register and scan QR codes
  • Backend API to process data and manage the system
  • Admin dashboard to monitor everything

I built the admin panel with PHP, vanilla JavaScript, Bootstrap, and MySQL. It worked. It got me my degree. But it was 2023 code.

Fast forward to 2026. I've spent 2 years building enterprise systems at Summit Media with Vue 3, Nuxt 3, TypeScript, and modern patterns. Looking back at my capstone code?

I could do so much better.

So I rebuilt the admin dashboard—not because I had to, but because I wanted to see exactly how much I'd grown.


THE CHALLENGE

Constraint: Recreate the EXACT same features and UI
Goal: Use modern patterns I learned professionally
Tech Stack: Nuxt 4, Vue 3, TypeScript, Pinia, Tailwind CSS (Frontend) + Express.js, JWT, MySQL (Backend)

Features to Recreate:

Dashboard Analytics

  • Real-time statistics for passengers, drivers, rides, and admins
  • Interactive charts and metrics
  • Live updates without page refresh

User Management

  • Manage passenger accounts
  • Manage driver accounts
  • Manage admin accounts
  • Role-based access control

Ride Tracking

  • View all rides in real-time
  • Monitor ride status (pending, active, completed)
  • Detailed ride information and history

Authentication

  • Secure login system
  • Protected routes with middleware
  • Session management

Responsive Design

  • Mobile-friendly interface
  • Works on all screen sizes
  • Touch-optimized interactions

WHAT I BUILT

The Old Way (2023 Capstone)

Tech Stack:
- PHP (backend + frontend logic)
- Vanilla JavaScript (DOM manipulation)
- Bootstrap (styling)
- MySQL (database)

Architecture:
- Monolithic PHP files
- Mixed concerns (HTML + PHP + SQL)
- Manual DOM updates
- No type safety
- ~2500 lines of mixed code

The New Way (2026 Rebuild)

Frontend:
- Nuxt 4 (framework)
- Vue 3 (reactive UI)
- TypeScript (type safety)
- Pinia (state management)
- Tailwind CSS (utility-first styling)
- Lucide Icons (modern iconography)
- ~1400 lines of organized, typed code

Backend (Separate Repository):
- Express.js (Node.js framework)
- JWT authentication with bcryptjs
- MySQL database
- RESTful API endpoints
- CORS support
- Comprehensive error handling

Architecture:
- Decoupled frontend and backend
- Component-based design (frontend)
- MVC pattern (backend)
- Reactive state management
- Full type safety

THE TECHNICAL DEEP DIVE

1. Component Architecture vs Procedural Code

Old approach (PHP):

<?php
// users.php - Everything in one file
if ($_GET['action'] == 'list') {
    $users = mysqli_query($conn, "SELECT * FROM users");
    while ($row = mysqli_fetch_assoc($users)) {
        echo "<tr><td>" . $row['name'] . "</td></tr>";
    }
}
?>

New approach (Vue/Nuxt):

// components/DataTable.vue - Reusable component
<template>
  <Table>
    <TableRow v-for="user in users" :key="user.id">
      <TableCell>{{ user.name }}</TableCell>
    </TableRow>
  </Table>
</template>

<script setup lang="ts">
const { users } = await useUsers(); // Type-safe composable
</script>

Why this is better:

  • Reusable across the app
  • Type-safe (TypeScript catches errors)
  • Reactive (updates automatically when data changes)
  • Testable (can test components in isolation)

2. State Management: Global Variables → Pinia

Old approach:

// Global variables scattered everywhere
var currentUser = null;
var rideData = [];

function loadRides() {
  $.ajax({
    url: "api/rides.php",
    success: function (data) {
      rideData = data;
      updateUI();
    },
  });
}

New approach:

// stores/rides.ts - Centralized, typed state
export const useRidesStore = defineStore("rides", () => {
  const rides = ref<Ride[]>([]);
  const loading = ref(false);

  async function fetchRides() {
    loading.value = true;
    rides.value = await $fetch("/api/rides");
    loading.value = false;
  }

  return { rides, loading, fetchRides };
});

Why this is better:

  • Single source of truth
  • Type-safe (Ride enforces structure)
  • Reactive (components auto-update)
  • Predictable state flow

3. Type Safety: Runtime Errors → Compile-Time Errors

Old approach (vanilla JS):

function displayRide(ride) {
  // What properties does ride have? Who knows!
  document.getElementById("status").innerHTML = ride.staus; // Typo!
  // This error only shows up when you run the code
}

New approach (TypeScript):

interface Ride {
  id: string;
  status: "pending" | "active" | "completed";
  passenger: User;
  driver: User;
}

function displayRide(ride: Ride) {
  console.log(ride.staus); // ❌ TypeScript error: Property 'staus' does not exist
  console.log(ride.status); // ✅ Correct
}

Why this is better:

  • Catches typos before running code
  • Self-documenting (you know what properties exist)
  • Better IDE autocomplete
  • Refactoring is safer

4. Styling: Custom CSS → Tailwind Utility Classes

Old approach (Bootstrap + custom CSS):

<div class="card user-card">
  <div class="card-header bg-primary">
    <h3 class="card-title">Users</h3>
  </div>
  <div class="card-body">
    <!-- content -->
  </div>
</div>

<style>
  .user-card {
    margin: 20px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  .card-title {
    font-weight: 600;
    color: white;
  }
</style>

New approach (Tailwind CSS):

<div class="m-5 rounded-lg shadow-sm bg-white">
  <div class="bg-blue-600 p-4 rounded-t-lg">
    <h3 class="font-semibold text-white">Users</h3>
  </div>
  <div class="p-4">
    <!-- content -->
  </div>
</div>

Why this is better:

  • No custom CSS files to maintain
  • Consistent spacing/colors (design system)
  • Faster to write (utility classes)
  • Smaller bundle size (unused classes purged)

WHAT I LEARNED

1. The Power of TypeScript

I didn't use TypeScript in my capstone. Now I can't imagine building without it.

Real example from this rebuild:

I was building the user management table. In vanilla JS, I kept making mistakes:

  • Accessing user.fullName when it was actually user.full_name
  • Trying to display ride.driver when it was sometimes null
  • Not knowing what properties existed without checking the API response

TypeScript caught all of these before I even ran the code. That's powerful.

2. Component-Based Thinking

In my original capstone, every page was a separate PHP file with duplicated code:

  • users.php had a table
  • drivers.php had the same table with different data
  • rides.php had the same table again

In Vue, I built one <DataTable> component and reused it everywhere with different data. Change one component, update all pages. That's the power of components.

3. State Management at Scale

In vanilla JS, I had data scattered everywhere:

var currentUser = null;
var allRides = [];
var filteredRides = [];
var selectedRide = null;

With Pinia, everything is organized:

useAuthStore(); // currentUser
useRidesStore(); // allRides, filteredRides, selectedRide

One source of truth. Predictable updates. Way easier to debug.

4. Modern CSS is Fast

Tailwind CSS let me recreate the exact same UI in about 1/3 the time.

Instead of:

  1. Write HTML
  2. Add class names
  3. Switch to CSS file
  4. Write styles
  5. Go back to HTML
  6. Adjust class names

I just:

  1. Write HTML with utility classes
  2. Done

The final bundle is actually smaller because Tailwind purges unused classes.

5. The Value of Rebuilding

This wasn't just a "portfolio project." It was a learning checkpoint.

By rebuilding something I knew intimately, I could focus on:

  • How to solve problems, not what to build
  • Why modern patterns are better, not just that they exist
  • Where I've grown as a developer

The original took me 2 months in 2023.
This rebuild took me 2 weeks in 2026.

That's growth.


THE RESULTS

Before (2023 - PHP/Vanilla JS)

  • ~2500 lines of mixed HTML/PHP/JavaScript
  • No type safety
  • Manual DOM manipulation
  • Duplicated code across pages
  • Hard to maintain and extend

After (2026 - Nuxt 4/TypeScript)

  • ~1400 lines of organized, typed code
  • Full TypeScript type safety
  • Component-based architecture
  • Reactive state management
  • Pinia for predictable data flow
  • Tailwind for consistent styling
  • Easy to maintain and extend

What This Proves

Not just that I can learn new tech, but that I understand why modern patterns exist.

I didn't just follow a tutorial. I rebuilt something I knew deeply and saw the improvements firsthand:

  • Faster development
  • Fewer bugs
  • Easier maintenance
  • Better developer experience

TECH STACK

Frontend Framework: Nuxt 4
UI Framework: Vue 3 (Composition API)
Language: TypeScript
State Management: Pinia
Styling: Tailwind CSS
Icons: Lucide Vue Next
API Client: Custom composables with error handling


TRY IT YOURSELF

View Live Demo   View Source


WHAT I'D DO DIFFERENTLY NEXT TIME

If I rebuilt this again today:

Add Real-Time Updates Use WebSockets instead of polling for truly real-time ride tracking

Implement E2E Testing Add Playwright tests to ensure features don't break

Add Data Visualization More interactive charts using D3.js or Chart.js

Optimize Bundle Size Implement lazy loading for routes and components

Add Offline Support Service workers for offline dashboard access

But honestly? For a personal learning project, I'm proud of where it is.


THE BIGGER PICTURE

This project represents 3 years of growth:

2023: Built my capstone with the tools I knew
2024: Learned modern frameworks professionally
2026: Rebuilt my capstone with everything I've learned

The cycle continues. What will I know in 2029 that makes this rebuild look outdated?

That's the exciting part—there's always more to learn.


Want to discuss modern refactoring or TypeScript patterns? Let's connect →

2026