DevPush

Creating your first Vue application

Introduction

This guide provides an example Vue.js project that you can create to support your current learning and development of the Vue.js framework.

This is meant to be an introduction as it involves creating a basic app. Follow through and read the details for each file to get an understanding of how the Vue.js code works.

Vue.js DevSites application

Requirements

Development tools and software are required to start the development of this Vue.js project on your PC / Laptop.

Here is a list of things needed to follow this guide.

Alternative applications can be used to the list above, they need to do the same thing.

Once the above applications have been installed and setup you are ready to begin creating the Vue.js app in the next section.

Creating the project

To begin bring up the terminal application such as Git Bash, find a suitable folder to store your development projects, and then call the below command to generate the Vue.js application.

 npm create vue@latest

After calling the command you will be prompted with questions, the image below shows the selection used for the app in this guide.

Here is a breakdown of each question, the answer selected, and the reason why.

Project name: devsites - The name of the project and folder name to contain all project files.

Add Typescript: Yes - TypeScript is now the standard for Vue.js applications and should be considered a requirement when developing JavaScript applications.

Add JSX Support: No - JSX isn't used for Vue.js applications so select No, JSX is the standard however when it comes to React applications.

Add Vue Router for Single Page Application development?: Yes - In most cases when you build a Vue.js application you will use the Vue Router so select.

Add Pinia for state management: No - The app being built is small therefore state management won't be used.

Add Vitest for Unit Testing?: No - No unit tests will be added as the focus is on building the app.

Add an End-To-End Testing Solution?: No - Same as unit tests, no end-to-end testing will be added to this app.

Add ESLint for code quality?: Yes - It's best to lint code to ensure coding standards are followed.

Add Prettier for code formatting?: Yes - Best used to ensure code is formatted correctly.

After generating the project go into the project by using the change directory command.

cd devsites

The app will require node modules to be installed, run the npm install command to perform that action.

npm install

After modules have been installed the app can now be run, use the npm run dev to start up the app.

npm run dev

You should see the URL for the app, typically it's http://localhost:5173. Go to the URL in your web browser to load the default vue.js app which will look like the following.

Building the devsites app

As the Vue.js app is up and running you can now begin coding the devsites app. Start by opening up the code editor with the devsites project loaded so you can see all the folders and files that make up the app.

Removing the default website code

The Vue.js has some default files that can be removed as they are used to show the default website you see in the web browser.

Go to the App.vue file inside the devsites folder and add the following code to clear the default header code.

<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>

<template>
  <header>Header!</header>
  <RouterView />
</template>

Go to the HomeView.vue file inside the views folder and clear the default code using the following code.

<script setup lang="ts">
</script>

<template>
  <main>Main!</main>
</template>

If you check the website you will see all the site elements have cleared and you can only see text within the header and main HTML tags.

The next task is to remove the AboutView page, start by going to the routes index.ts file and removing the AboutView route, leaving the HomeView as that will be used.

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    }
  ]
})

export default router

The next step to follow will be deleting files rather than changing the content inside of the files.

After removing the route for AboutView the AboutView.vue file inside the views folder can now be deleted.

Now go to the components folder and delete everything inside it which should consist of the following.

  • WelcomeItem.vue
  • TheWelcome.vue
  • HelloWorld.vue
  • icons folder

Environment variables file

All the files and code that made up the default website should be removed.

Now building the devsites app can begin.

.env file

To start, create a .env file inside the devsites folder, and add the VITE_API_URL environment variable with its value.

This provides the API URL to the app so it knows where to fetch the devsite data which will be shown in the app.

VITE_API_URL=https://api.devpushprojects.com

Now go to the assets folder then the main.css and replace the contents with the following.

This CSS will apply styles globally to all targeted HTML tags.

@import './base.css';

html, body {
  font-family: ui-sans-serif, system-ui, sans-serif;
}

body {
  margin: 0;
}

a {
  color: #000;
  text-decoration: none;
}

h1 {
  font-weight: 800;
}

img {
  width: 238px;
  height: 149px;
}

Types

Create a new folder inside of devsites named types. Inside of the new types folder create a file named index.ts.

types / index.ts

The index.ts file is where the Site and SiteCategory interfaces will be created which will be used to set a type for the sites and category data retrieved from the API endpoints.

export interface Site {
  id: number
  site_category_id: number
  name: string
  url: string
  image_url: string
}

export interface SiteCategory {
  id: number
  name: string
}

Utils

API code is required, this will be used to call the API and retrieve the data that the app will use.

utils / api.ts

Create a utils folder inside the devsites folder then create a new file named api.ts.

Add the following code, this is a function that uses JavaScript to send requests and localStorage to cache the API requests so the data is stored locally in the web browser.

In terms of localStorage, I don't recommend it for building apps, it's used here as an easy and quick way to cache API data.

const sendRequest = async <T>(url: string, config: RequestInit = {}): Promise<T | null> => {
  const cache: string | null = localStorage.getItem(url)

  if (cache) {
    return JSON.parse(cache)
  }

  const response = await fetch(url, config)

  if (response) {
    const json = await response.json()
    const data = json['data'] as T

    localStorage.setItem(url, JSON.stringify(data))

    return data
  }
  return null
}

export default sendRequest

utils / devSiteApi.ts

Create another file in the utils folder named devSiteApi.ts which will use the sendRequest function imported from the api.ts file.

This code has two functions, the getSites and getSiteCategories functions which will be used to get site and category data from the API.

import type { Site, SiteCategory } from '@/types'
import sendRequest from './api'

const apiUrl = import.meta.env.VITE_API_URL

export const getSites = async (categoryId: number) => {
  const endpoint: string = categoryId ? `categorized/${categoryId}` : ''
  const url: string      = `${apiUrl}/devsites/${endpoint}`

  return await sendRequest<Site[]>(url)
}

export const getSiteCategories = async () => {
  const url: string = `${apiUrl}/devsites/categories`
  return await sendRequest<SiteCategory[]>(url)
}

Components

Now API code is required which will be used to call the API and retrieve the data.

components / SiteBar.vue

The site bar component will show a list of categories for all the dev sites. This component will use the getSiteCategories function from the utils/devSiteApi.ts file to get the categories. As each category is looped the category is set up as a router link so it will navigate to a different page.

<script setup lang="ts">
import { onMounted, ref, type Ref } from 'vue'
import { onBeforeRouteUpdate, RouterLink } from 'vue-router'
import type { SiteCategory } from '@/types'
import { getSiteCategories } from '@/utils/devSiteApi'

const props = defineProps<{ categoryId: number }>()

const siteCategories: Ref<SiteCategory[] | null> = ref([])
const currentCategoryId: Ref<number> = ref(props.categoryId)

onMounted(async () => {
  siteCategories.value = await getSiteCategories()
})

onBeforeRouteUpdate(async (to, from) => {
  if (to.params.id !== from.params.id && to.params.id != '0') {
    currentCategoryId.value = Number(to.params.id)
  }
})
</script>

<template>
  <div v-if="siteCategories && siteCategories.length > 0" class="categories">
    <h2>Categories</h2>
    <div class="category-links">
      <RouterLink
        :to="{ name: 'home' }"
        class="category"
        :data-state="currentCategoryId === 0 ? 'active' : 'normal'"
      >
        All
      </RouterLink>
      <RouterLink
        v-for="siteCategory in siteCategories"
        :to="{ name: 'category-sites', params: { id: siteCategory.id } }"
        class="category"
        :data-state="currentCategoryId === siteCategory.id ? 'active' : 'normal'"
      >
        {{ siteCategory.name }}
      </RouterLink>
    </div>
  </div>
</template>

<style>
.categories {
  display: flex;
  flex-direction: column;
  width: 200px;
  padding: 0 0 0 40px;
}

h2 {
  margin-bottom: 10px;
}

.category-links {
  display: flex;
  flex-direction: column;
}

.category {
  display: inline-block;
  margin-bottom: 20px;
}

.category:hover {
  cursor: pointer;
  text-decoration: underline;
}

.router-link-active {
  font-weight: bold;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
}
</style>

components / SiteList.vue

This component will get the devsites from the getSites function, this component will loop through each devsite and output the devsite image, name, and link which takes the user to the actual site after it's clicked on.

<script setup lang="ts">
import { onMounted, ref, type Ref } from 'vue'
import { onBeforeRouteUpdate } from 'vue-router'
import type { Site } from '@/types'
import { getSites } from '@/utils/devSiteApi'

const props = defineProps<{ categoryId?: number }>()

const sites: Ref<Site[] | null> = ref([])

onMounted(async () => {
  sites.value = await getSites(props.categoryId ?? 0)
})

onBeforeRouteUpdate(async (to, from) => {
  if (to.params.id !== from.params.id && to.params.id != '0') {
    sites.value = await getSites(Number(to.params.id))
  }
})
</script>

<template>
  <div class="sites">
    <a v-for="site in sites" :href="site.url" target="”_blank”" class="site">
      <img :src="site.image_url" />
      <div class="site-name">{{ site.name }}</div>
    </a>
  </div>
</template>

<style>
.sites {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 40px 20px;
  width: 1000px;
  justify-items: start;
}

.site {
  width: auto;
  padding: 24px;
  border-radius: 5px;
  border: 1px solid #d1d5db;
  background-color: #f8fafc;
  box-shadow: 2px 2px 8px 1px #ddd;
}

img {
  width: 238px;
  height: 149px;
}

.site-name {
  margin-top: 10px;
  font-weight: bold;
}
</style>

components / SitePanel.vue

This component will import the SiteList and SideBar components and output them. If the category ID is passed into the SitePanel component it will then read and use it to filter the sites by category ID and highlight the current category from the full list of categories in the sidebar.

<script setup lang="ts">
import SiteList from '@/components/SiteList.vue'
import SideBar from '@/components/SiteBar.vue'

const props = defineProps<{ categoryId?: number }>()
</script>

<template>
  <SiteList :categoryId="props.categoryId ?? 0" />
  <SideBar :categoryId="props.categoryId ?? 0" />
</template>

Views

The components inside the components folder will now be imported and used in the view components in the views folder.

views/ HomeView.vue

Go to the HomeVIew.vue file and import and output the SitePanel component.

<script setup lang="ts">
import SitePanel from '@/components/SitePanel.vue'
</script>

<template>
  <SitePanel />
</template>

views / CategorySiteView.vue

Import the SitePanel component and output it within the template tags passing in the category ID.

<script setup lang="ts">
  import { useRoute } from 'vue-router'
  import SitePanel from '../components/SitePanel.vue'
  import { onMounted, ref, type Ref } from 'vue'

  const route = useRoute()
  const categoryId: Ref<number> = ref(0)

  onMounted(() => {
    categoryId.value = Number(route.params.id)
  })
</script>

<template>
  <SitePanel v-if="categoryId" :categoryId="categoryId" />
</template>

Router

router / index.ts

Go to the index.ts inside the router folder and import the new CategorySiteView component into this file.

After importing the component set up a new route for the category path which links to the CategorySiteView component.

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import CategorySiteView from '@/views/CategorySiteView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/category/:id',
      name: 'category-sites',
      component: CategorySiteView
    }
  ]
})

export default router

App

App.vue

Go to the index.ts inside the router folder and import the new CategorySiteView component into this file.

<script setup lang="ts">
  import { RouterView } from 'vue-router'
</script>

<template>
  <div>
    <header>
      <h1>DevSites</h1>
      <div>Technologies and services related to PHP development</div>
    </header>
    <main>
      <RouterView />
    </main>
  </div>
</template>

<style>
header {
  padding: 20px 40px;
}

main {
  display: flex;
  margin-bottom: 40px;
  padding-bottom: 40px;
}

header,
main {
  max-width: 1200px;
  margin: 0 auto;
  padding-left: 40px;
  padding-right: 40px;
}
</style>

Final result

The coding task will be complete and the app should look like the screenshot below.

Clicking on the category links on the right side menu will be functional allowing you to filter all the dev sites shown based on the category type selected.

Clicking on any of the dev sites will open up a new web browser tab and load the website selected.

If you run into any issues following this guide the final code can be found on the devsites-vue GitHub page, you can easily download a zip file containing the final code for this app.

Conclusion

After completion of this guide, you will have developed a simple app that shows developer websites and categories that can be used to filter those same websites.

The goal is to explain the Vue code of an existing app to provide an understanding of how an app works and how to build it with Vue.js using TypeScript.

Hopefully, you have found this useful for your continued learning and development in Vue.js.