Beginning VueJS

VueJS Workshop Notes

A practical guide to building interactive web applications with Vue 3, Vite, and modern tooling.

🚀 What is VueJS?

VueJS is a JavaScript framework for building Single Page Applications for the web. It uses HTML, CSS, JS and employs both component-based and declarative programmingmodels to build user interfaces.

It's a progressive framework — flexible and incrementally adoptable.

VueJS Use Cases:

  • • Enhancing static HTML without a build step
  • • Embedding as Web Components on any page
  • • Single-Page Applications (SPA)
  • • Fullstack / Server-Side Rendering (SSR)
  • • Jamstack / Static Site Generation (SSG)
  • • Targeting desktop, mobile, WebGL, and even the terminal

Before we begin: Install NodeJS — this is required to install JS libraries, frameworks, and packages via NPM throughout this workshop.

Understanding the Shift: Documents vs. Applications

Before we write our first line of code, it is essential to understand that we are not just learning a new tool; we are adopting a different mindset for how a website works. In the traditional approach, we build documents. In the Vue.js approach, we build an application.

🗂️

Traditional Website (MPA)

Think of a basic HTML website as a physical filing cabinet. Each URL is like pulling out a complete, finished sheet of paper — every navigation is a fresh document.

Discard — Browser throws away the current page
Request — Asks the server for the new page
Reload — Entire page redraws from scratch, even unchanged parts like the header and footer
Full page disruption on every navigation
🧩

Vue.js App (SPA)

One persistent application surface. Vue manages a single HTML shell (index.html) and uses JavaScript to swap content inside it — no full reloads.

Header & Footer stay mounted — not re-rendered
Only the changed component is swapped out
Navigation feels instant and native — like a mobile app
Lightning-fast, fluid navigation

The Concept of 'Components'

The secret to the SPA model is Components. In Vue, we break everything down into independent, interlocking blocks — like puzzle pieces that snap together to form a complete UI.

Everything is a Component

🔘
Button
Smallest unit
📌
Header
Shared across routes
📋
Sidebar
Holds sub-components
📄
Page
A component of components

Key insight: When navigating from 'Home' to 'About', Vue keeps the Header and Footer mounted and simply swaps the main content component. This uses far less data and creates a smooth, fluid experience that feels like a native mobile app rather than a clunky sequence of page loads.

Setting up with Vite

Vite (French: "veet") is a local development server created by Evan You, the creator of Vue.js. It supports TypeScript, JSX, and uses Rollup/esbuild for bundling.

Key features: Hot Module Replacement (HMR), built-in SSR support, listens on port 5173 by default.

Terminal — Create Vue Project
# Create Vue project with Vite
$ npm create vite@latest my-vue-app
# Select: Vue → JavaScript

# Install dependencies
$ cd my-vue-app && npm install

# Start dev server
$ npm run dev
# → http://localhost:5173

After setup, you'll find a src folder with App.vue — your entry point.

src/App.vue
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <HelloWorld msg="Vite + Vue" />
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
</style>

Understanding Reactivity

In Vue, reactive variables automatically update the UI when their values change.

Reactive variable with ref()
<script setup>
import { ref } from 'vue'

defineProps({ msg: String })
const count = ref(0)
</script>

<template>
  <h1>{{ msg }}</h1>
  <button @click="count++">count is {{ count }}</button>
</template>
Live Demo — Reactive Counter
0

Computed double: 0

Mustache syntax: Use {{ variable }}in templates to display reactive data. Vue handles the DOM updates automatically — no need forgetElementById().

Plain JS vs Vue — Event Handling
// Plain JavaScript (tedious)
let count = 0
const el = document.getElementById('count')
function increment() {
  count++
  el.innerHTML = count  // Manual DOM update
}

// Vue (declarative)
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
  <button @click="count++">{{ count }}</button>
</template>

🧩 Component-Based Development

Vue uses Single-File Components (.vue) that combine template, script, and styles in one file.

Counter.vue — Reusable Component
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <button @click="count++">
    count is {{ count }}
  </button>
</template>

<style scoped>
button {
  background-color: #4CAF50;
  color: white;
  padding: 15px 32px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

v-model — Two-Way Binding Demo

Hello, stranger! 👋

Scoped CSS: The <style scoped> attribute ensures styles only apply to the current component, preventing global CSS conflicts.

Building a Todo List

TodoList.vue
<script setup>
import { ref } from 'vue'
const todos = ref([])
const todo = ref("")

function addTodo() {
  todos.value.push(todo.value)
  todo.value = ""
}
</script>

<template>
  <input v-model="todo" placeholder="Add todo..." />
  <button @click="addTodo">Add</button>
  <ul>
    <li v-for="t in todos" :key="t">{{ t }}</li>
  </ul>
</template>
Interactive Todo List

No todos yet — add one above! ✨

v-model

Binds input value to component state. Changes in input update state; state changes update input.

v-for

Renders a list by looping over an array. Always use :key for performance.

📦 Props — Component Communication

Props allow parent components to pass data down to child components.

Parent → Child with Props
<script setup>
// Parent: App.vue
import TodoItem from './components/TodoItem.vue'
const todos = ref(['Learn Vue', 'Build app'])
</script>

<template>
  <TodoItem v-for="todo in todos" :key="todo" :todo="todo" />
</template>

<!-- Child: TodoItem.vue -->
<script setup>
defineProps({ todo: String })
</script>

<template>
  <li>{{ todo }}</li>
</template>

Prop Definition Patterns

// Single prop
defineProps({ msg: String })

// Multiple props with types
defineProps({
  msg: String,
  count: Number,
  isActive: Boolean,
  items: Array,
  config: Object
})

// With default values & validation
defineProps({
  msg: { type: String, required: true },
  count: { type: Number, default: 0 }
})

Tip: Use :propName (shorthand for v-bind:propName) to pass dynamic values. Use plain propName="value" for static strings.

🎨 Styling with Tailwind CSS

Tailwind CSS is a utility-first framework — compose designs using small, pre-built classes directly in your markup.

Tailwind-Powered Button
<template>
  <button 
    @click="count++"
    class="bg-blue-500 hover:bg-blue-700 text-white 
           font-bold py-2 px-4 rounded transition"
  >
    count is {{ count }}
  </button>
</template>

Live Preview

Pro Tip: Install the Tailwind CSS IntelliSense VSCode extension for autocomplete, syntax highlighting, and linting. Learn more at tailwindcss.com.

🔗 Client-Side Routing with Vue Router

In SPAs, routing happens on the client — the browser doesn't reload. Vue Router maps URLs to components and handles navigation without full page refreshes.

router/index.js — Route Configuration
// main.js
import { createRouter, createWebHashHistory } from 'vue-router'
import Home from './components/Home.vue'
import About from './components/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

createApp(App).use(router).mount('#app')

Router Components

  • <RouterView /> — Renders the matched component for the current route
  • <RouterLink to="/path">— Declarative navigation (replaces <a> tags)
  • useRouter() / useRoute() — Composition API hooks for programmatic navigation

🌐 Making API Requests with Fetch

The Fetch API is built into modern browsers — no library needed. It returns Promises, making it perfect for async/await patterns.

GET — Fetch GitHub Users
<script setup>
import { ref } from 'vue'
const users = ref([])

fetch('https://api.github.com/users')
  .then(res => res.json())
  .then(data => { users.value = data })
  .catch(err => console.error(err))
</script>

<template>
  <ul>
    <li v-for="user in users" :key="user.id">
      {{ user.login }}
    </li>
  </ul>
</template>
Demo: Fetch Random Joke
Demo: GitHub Users

Best Practice: Always handle loading and error states when fetching data. Use try/catch with async/await for cleaner error handling than .catch() chains.

📚 Additional Resources