DEV Community

Cover image for Create a Modern Application with Django and Vue - Part Two
Eric Hu
Eric Hu

Posted on • Originally published at ericsdevblog.com

Create a Modern Application with Django and Vue - Part Two

Now that we are done with the backend, it is time for us to create the frontend part of our application. In this tutorial, we are going to use Vue.js to create the frontend application. Again, let's start with a brief review. If you've never used the framework before, please consider going through the Vue.js For Beginners tutorial first.

You can download the source code for this tutorial here:

Download the Source Code

A Brief Review on Vue.js

Vue.js is a front-end JavaScript framework that provides us with a simple component-based system, which allows us to create complex user interfaces. Component-based means that the root component (App.vue) can import other components (files with extension .vue), and those other components can import more components, which allows us to create very complex systems.

A typical Vue file contains three sections, the <template> section includes HTML codes, the <script> section includes JavaScript Codes, and the <style> section includes the CSS codes.

In the <script> section, we can declare new bindings inside the data() function. These bindings can then be displayed inside the <template> section using the double curly braces syntax ({{ binding }}). The bindings declared inside the data() method will automatically be wrapped inside Vue's reactivity system. Meaning that when the value of the binding changes, the corresponding component will automatically be rendered, without having to refresh the page.

The <script> section can also contain methods other than data(), such as computedpropsmethods and so on. And the <template> also allows us to bind data using directives such as v-bindv-on and v-model. If you don't know what they are, please consider going through this tutorial first: Vue.js For Beginners.

Create a New Vue.js Project

In the Vue.js For Beginners tutorial, we installed and created a Vue app using the Vue command-line tool. This time, we are going to do things differently. We are going to use a frontend build tool called Vite (pronounced as "veet", the French word for fast.), which is created by the same person who created Vue.js.

Go into the frontend folder, and run the following command:

npm init vue@latest
Enter fullscreen mode Exit fullscreen mode

You will be prompted with multiple options, for our project, we only need to add Vue Router:

✔ Project name: … <your-project-name>
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit testing? … No / Yes
✔ Add Cypress for both Unit and End-to-End testing? … No / Yes
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formating? … No / Yes

Scaffolding project in ./<your-project-name>...
Done.
Enter fullscreen mode Exit fullscreen mode

If you are more comfortable with a strong type language, you can elect to install TypeScript. If you need auto-correct for your code, you can install ESlint and Prettier as well.

This process will generate a package.json file in your project, which stores the required packages and their versions. You need to install these packages inside your project.

cd <your-project-name>
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

One more thing before we start creating the frontend app. We are using a CSS framework called TailwindCSS in our project. To install it, run the following command:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

This will generate two files, tailwind.config.js and postcss.config.js. This is not a tutorial on CSS or Tailwind, I assume you already know how to use them, and what PostCSS is. If not, please consider reading their documentations. Tailwind: (https://tailwindcss.com/docs/editor-setup). PostCSS: (https://github.com/postcss/postcss/tree/main/docs)

Go to tailwind.config.js, and add the path to all of your template files:

module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode

Create a ./src/index.css file and add the @tailwind directives for each of Tailwind’s layers.

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Import the newly-created ./src/index.css file in your ./src/main.js file.

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import './main.css'

const app = createApp(App)

app.use(router)

app.mount('#app')
Enter fullscreen mode Exit fullscreen mode

Now you should be able to use Tailwind inside the .vue files. Let's test that.

<template>
  <header>
    ...
    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <h1 class="text-3xl font-bold underline">Hello world!</h1>
      ...
    </div>
  </header>
  ...
</template>
Enter fullscreen mode Exit fullscreen mode

We added an <h1> heading after <HelloWorld>, and the heading uses Tailwind classes.

https://www.ericsdevblog.com/wp-content/uploads/2022/03/Screen-Shot-2022-03-02-at-4.59.48-PM-1024x558.png

Vue Router

Also, notice that this time, our project directory is a little bit different.

https://www.ericsdevblog.com/wp-content/uploads/2022/03/image.png

Inside the src directory, we have a router and a views folder. The router directory contains an index.js file. This is where we can define our routers. Each router will point to a view component that is inside the views directory, and the view can then extend to other components inside the components directory.

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
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router
Enter fullscreen mode Exit fullscreen mode

To invoke a defined router, look inside the App.vue file. Instead of the <a> tag, we use <RouterLink> which is imported from the vue-router package.

<script setup>
import { RouterLink, RouterView } from "vue-router";
...
</script>

<template>
  <header>
    ...
    <div class="wrapper">
      ...
      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
      </nav>
    </div>
  </header>

  <RouterView />
</template>
Enter fullscreen mode Exit fullscreen mode

When the page is being rendered, the <RouterView /> tag will be replaced with the corresponding view.

If you don't want to import these components, simply use <router-link to=""> and <router-view> instead. Personally, I prefer this way because I always forget to import them.

Create Routes

For our blog application, we need to create at least 6 pages. We need a home page that displays a list of recent pages, a categories/tags page that displays all categories/tags, a category/tag page that displays a list of posts that belongs to the category/tag, and finally, a post page that displays the post content as well as the comments.

So, these are the routers I created.

import { createRouter, createWebHistory } from "vue-router";
import HomeView from "@/views/main/Home.vue";
import PostView from "@/views/main/Post.vue";
import CategoryView from "@/views/main/Category.vue";
import TagView from "@/views/main/Tag.vue";
import AllCategoriesView from "@/views/main/AllCategories.vue";
import AllTagsView from "@/views/main/AllTags.vue";

const routes = [
  {
    path: "/",
    name: "Home",
    component: HomeView,
  },
  {
    path: "/category",
    name: "Category",
    component: CategoryView,
  },
  {
    path: "/tag",
    name: "Tag",
    component: TagView,
  },
  {
    path: "/post",
    name: "Post",
    component: PostView,
  },
  {
    path: "/categories",
    name: "Categories",
    component: AllCategoriesView,
  },
  {
    path: "/tags",
    name: "Tags",
    component: AllTagsView,
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

Enter fullscreen mode Exit fullscreen mode

Please note that in this article, we are only creating the frontend interface, we are not dealing with data transfer just yet, so don't worry about how to find the correct post/category/tag right now.

Views and Components

This is the frontend UI that I created for this project, you can either use my code directly or if you don't like it, you can follow this tutorial on Vue.js (Vue.js for Beginners) and create your own.

  • Images

https://www.ericsdevblog.com/wp-content/uploads/2022/04/Screen-Shot-2022-02-13-at-7.15.33-PM.png

https://www.ericsdevblog.com/wp-content/uploads/2022/04/Screen-Shot-2022-02-16-at-10.20.36-AM.png

https://www.ericsdevblog.com/wp-content/uploads/2022/04/Screen-Shot-2022-02-13-at-7.13.52-PM.png

https://www.ericsdevblog.com/wp-content/uploads/2022/04/Screen-Shot-2022-02-16-at-10.20.18-AM.png

https://www.ericsdevblog.com/wp-content/uploads/2022/04/Screen-Shot-2022-02-13-at-7.15.21-PM.png

https://www.ericsdevblog.com/wp-content/uploads/2022/04/Screen-Shot-2022-02-13-at-7.14.07-PM.png

https://www.ericsdevblog.com/wp-content/uploads/2022/04/Screen-Shot-2022-02-13-at-7.14.34-PM.png


  • App.vue
  <template>
    <div class="container mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl xl:px-0">
      <div class="flex flex-col justify-between h-screen">
        <header class="flex flex-row items-center justify-between py-10">
          <div class="nav-logo text-2xl font-bold">
            <router-link to="/">Django Vue Starter Blog</router-link>
          </div>
          <div class="nav-links hidden sm:block">
            <router-link
              to="/"
              class="mx-2 font-sans font-medium hover:underline hover:text-teal-700"
              >Home</router-link
            >
            <router-link
              to="/categories"
              class="mx-2 font-sans font-medium hover:underline hover:text-teal-700"
              >Category</router-link
            >
            <router-link
              to="/tags"
              class="mx-2 font-sans font-medium hover:underline hover:text-teal-700"
              >Tag</router-link
            >
          </div>
        </header>

        <router-view />

        <footer class="flex flex-col place-items-center mt-5 py-5 border-t-2">
          <div class="mb-3 flex space-x-4">
            <i
              class="fa-brands fa-linkedin text-3xl text-gray-700 hover:text-teal-700"
            ></i>
            ...
          </div>
          <div class="mb-3 flex space-x-1 text-sm text-gray-700">
            <div>
              <a
                href="<https://www.ericsdevblog.com>"
                class="hover:underline hover:text-teal-700"
                >Eric Hu</a
              >
            </div>
            <div></div>
            <div>© 2022</div>
            <div></div>
            <a href="/" class="hover:underline hover:text-teal-700"
              >Vue.js Starter Blog</a
            >
          </div>
        </footer>
      </div>
    </div>
  </template>

  <script>
  export default {
    ...
  };
  </script>

Enter fullscreen mode Exit fullscreen mode
  • views/main/Home.vue
  <template>
    <div class="home">
      <h1 class="text-5xl font-extrabold mb-2">Recent Posts</h1>
      <p class="text-gray-500 text-lg mb-5">
        A blog created with Django, Vue.js and TailwindCSS
      </p>

      <post-list></post-list>
    </div>
  </template>

  <script>
  // @ is an alias to /src
  import PostList from "@/components/PostList.vue";

  export default {
    components: { PostList },
    name: "HomeView",
  };
  </script>

Enter fullscreen mode Exit fullscreen mode
  • views/main/AllCategories.vue
  <template>
    <div class="flex flex-col place-content-center place-items-center">
      <div class="py-8 border-b-2">
        <h1 class="text-5xl font-extrabold">All Categories</h1>
      </div>
      <div class="flex flex-wrap py-8">
        <router-link
          class="my-2 mr-5 text-sm font-medium uppercase text-teal-500 hover:underline hover:text-teal-700"
          to="/category"
          >Category Name</router-link
        >
        <router-link
          class="my-2 mr-5 text-sm font-medium uppercase text-teal-500 hover:underline hover:text-teal-700"
          to="/category"
          >Category Name</router-link
        >
        ...
      </div>
    </div>
  </template>

  <script>
  export default {
    name: "CategoriesView",
  };
  </script>

Enter fullscreen mode Exit fullscreen mode
  • views/main/Category.vue
  <template>
    <div>
      <h1 class="text-5xl font-extrabold mb-2">Category Name</h1>
      <p class="text-gray-500 text-lg mb-5">
        A blog created with Django, Vue.js and TailwindCSS
      </p>

      <post-list></post-list>

    </div>
  </template>

  <script>
  // @ is an alias to /src
  import PostList from "@/components/PostList.vue";

  export default {
    components: { PostList },
    name: "CategoryView",
  };
  </script>

Enter fullscreen mode Exit fullscreen mode
  • views/main/Post.vue
  <template>
    <div class="home">
      <div class="flex flex-col place-items-center border-b-2">
        <!-- Featured Image and title -->
        <img src="..." class="w-full my-5" />
        <h1 class="text-center text-5xl font-extrabold mb-5">
          Post Title
        </h1>
        <p class="text-gray-500 text-lg mb-2">
          March 3, 2022 - By Eric Hu
        </p>
      </div>

      <!-- Tags -->
      <div class="flex flex-wrap my-4">
        <div class="mr-5 text-sm font-medium">Tags:</div>
        <router-link
          class="mr-5 text-sm font-medium uppercase text-teal-500 hover:underline hover:text-teal-700"
          to="/tag"
          >Tag Name</router-link
        >
        ...
      </div>

      <!-- Main content -->
      <div class="py-5 font-serif space-y-4">
        Lorem Lipsum ...
      </div>

      <!-- Like, Comment and Share -->
      ...

      <!-- Comment Section -->
      ...
    </div>
  </template>

  <script></script>

Enter fullscreen mode Exit fullscreen mode
  • components/PostList.vue
  <template>
    <div class="post-list">
      <ul v-if="publishedPosts" class="divide-y divide-gray-200">
        <li class="py-12">
          <article>
            <div
              class="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0"
            >
              <dl>
                <dt class="sr-only">Published on</dt>
                <dd
                  class="text-base font-medium leading-6 text-gray-500 dark:text-gray-400"
                >
                  <time>March 3, 2022</time>
                </dd>
              </dl>
              <div class="space-y-5 xl:col-span-3">
                <div class="space-y-6">
                  <div>
                    <h2 class="text-2xl font-bold leading-8 tracking-tight">
                      <router-link
                        class="text-gray-900"
                        to="/post"
                        >This is a post title</router-link
                      >
                    </h2>
                    <router-link
                      class="text-sm font-medium uppercase text-teal-500 hover:underline hover:text-teal-700"
                      to="/category"
                      >Category</router-link
                    >
                  </div>
                  <div class="prose max-w-none text-gray-500">
                    Lorem Lipsum ...
                  </div>
                </div>
                <div class="text-base font-medium leading-6">
                  <router-link
                    class="text-teal-500 hover:underline hover:text-teal-700"
                    to="/post"
                    >Read more →</router-link
                  >
                </div>
              </div>
            </div>
          </article>
        </li>
        ...
      </ul>
    </div>
  </template>

  <script>
  export default {
    name: "PostListComponent",
  };
  </script>

Enter fullscreen mode Exit fullscreen mode

Discussion (0)