環境構成(Django+Vue3+Vuetify+Axios)に沿った手順を順に説明します。
🌐 全体構成
vue_django/ # PJとして環境変数に設定
│
├── project/ # Django プロジェクト
│ ├── settings.py
│ ├── urls.py
│ └── ...
├── app/ # Django アプリ
│ ├── views.py
│ ├── urls.py
│ └── models.py
│
├── frontenv/ # Vue3 + Vuetify + Axios
│ ├── package.json
│ ├── src/
│ │ ├── main.js
│ │ ├── App.vue
│ │ └── views/
│ ├── public/
│ └── vue.config.js
│
├── static/ # Django 側静的ファイル出力先
└── templates/ # Django 側 HTML テンプレート出力先
環境構築
最初にDjangoの環境構築
プロジェクト名をprojectとし、アプリケーション名をappとして構築します。
mkdir vue_django
cd vue_django
export PJ=`pwd`
python -m venv ./venv
source venv/bin/activate
pip install django django-restframework
django-admin startproject project
chmod +x ./manage.py
./manage.py startapp appVueのリリース先を作成しておきます。
cd $PJ
mkdir static
mkdir templatesVue3 + Vuetify 環境構築
vueの環境を構築:
npm install @vue/cli
cd $PJ
vue create frontend
Vue CLI v5.0.9
? Please pick a preset: (Use arrow keys)
❯ Default ([Vue 3] babel, eslint)
Default ([Vue 2] babel, eslint)
Manually select features
axiosとrouterの導入:
cd $PJ/frontend
vue add axios routerVuetify 初期設定:
mkdir $PJ/frontend/public
cd $PJ/frontend/
vue add vuetify
? Choose a preset:
Vuetify 2 - Configure Vue CLI (advanced)
Vuetify 2 - Vue CLI (recommended)
Vuetify 2 - Prototype (rapid development)
Vuetify 3 - Vite (preview)
❯ Vuetify 3 - Vue CLI (preview)
$PJ/frontend/vue.config.jsを以下のように編集:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
publicPath: "/static",
outputDir: "../static",
assetsDir: "",
indexPath: "../templates/index.html",
transpileDependencies: true,
pluginOptions: {
vuetify: {
}
},
})
$PJ/frontend/src/main.js を以下のように編集:
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify'
import { loadFonts } from './plugins/webfontloader'
loadFonts()
createApp(App).use(router)
.use(router)
.use(vuetify)
.mount('#app')$PJ/frontend/src/App.vueを以下のように編集:
<template>
<router-view/>
</template>簡易なREST API連携の例
DjangoでSampleViewというOKのみを返すViewを作成し、それをVueで表示させるようにしてみます。
Vueのサンプルを作成
まずはじめに、Vue側のコードを作成します。
$PJ/frontend/src/views/SampleView.vue を作成:
<template>
<v-container>
<v-card class="ma-5 pa-5">
<v-card-title>Sample API Call</v-card-title>
<v-card-text>
<v-btn color="primary" @click="callApi">Call Django API</v-btn>
<div class="mt-4">Response: {{ response }}</div>
</v-card-text>
</v-card>
</v-container>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const response = ref('')
onMounted(async () => {
try {
const res = await axios.get('//localhost:8000/api/v1/sample/')
response.value = res.data;
} catch (err) {
response.value = 'Error: ' + err.message
}
});
</script>$PJ/frontend/src/router/index.js にルートを追加:
import { createRouter, createWebHistory } from 'vue-router'
import SampleView from '../views/SampleView.vue'
const routes = [
{
path: '/',
name: 'Home',
component: SampleView,
},
]
export default createRouter({
history: createWebHistory(),
routes,
})アプリをビルドしてDjangoのtemplatesおよびstaticへファイルを配置します。
cd $PJ/frontend/
npm run buildDjango 側のアプリ構築
Djangoの設定をしていきます。
$PJ/project/settings.py (変更箇所のみ記載しています):
# 他のホストからアクセスする場合に備えて * にした
ALLOWED_HOSTS = ['*']
# appと、rest_frameworkをついか
INSTALLED_APPS = [
'app',
'rest_framework',
# templatesのDIRSにVueのリリース先であるtemplatesの場所を指定
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / "templates"],
# 言語とタイムゾーンを指定
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
# Vueのcssやjsなどのリリース先であるstaticの場保を指定
STATIC_URL = 'static/'
STATICFILES_DIRS = [
BASE_DIR / "static",
]
$PJ/project/urls.py を編集します:
from django.contrib import admin
from django.urls import path, include, re_path
from django.conf import settings
from django.conf.urls.static import static
from app.views import IndexView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('app.urls')),
re_path(r"^.*$", IndexView.as_view()),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
次にappアプリの設定をします。
$PJ/app/views.py を編集します。
from django.shortcuts import render
# Create your views here.
from rest_framework import status, viewsets
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.generic.base import TemplateView
class IndexView(TemplateView):
template_name = "index.html"
class SampleAPIView(APIView):
def get(self, request):
return Response("OK", status=status.HTTP_200_OK)$PJ/app/urls.py を作成します:
from django.urls import path
from app.views import SampleAPIView
urlpatterns = [
path("sample/", SampleAPIView.as_view(), name="sample"),
]以上で REST Framework と SampleAPIView が動くようになったので、
Djangoの開発用サーバを起動します:
# Django サーバ起動
cd $PJ
./manage.py runserver 0.0.0.0:8000Django の開発サーバが起動したら、ブラウザで http://localhost:8000 にアクセスします。Vueのビルドで生成したtemplates/index.html が呼び出され static/以下のcssやVueのjsが読み込まれて、axiosが、http://localhost:8000/api/v1/sample/ にアクセスしてAPIでOKを得て、画面にOKが表示されます。
以上で、VueとDjangoのREST APIにアクセスするサンプルアプリが完成しました。
次に、DBと連携した本の一覧を表示するアプリの作成をVueのルーティングで振り分ける形で追加作成していきたいと思います。
動作確認:
Vue 側で axios 経由でDjangoのAPIのsampleの値「OK」を呼び出せます。
GET http://localhost:8000/

Booksアプリの作成
まずはSampleAPIViewでOKを得て表示される簡単なサンプルアプリを作りました。次は書籍を登録して一覧表示させるようなものを作って見ようと思います。
データベースの設定
ここではDjango標準搭載のSQLite3を使うことにします。
まず初めに、データベースへアクセスするための管理者用のアカウントを作成します。パスワードが短すぎたりすると注意がでてvalidationをbypassして良いか聞いてきますがここではyします。
cd $PJ
./manage.py makemigrations
./manage.py migrate
./manage.py createsuperuser
ユーザー名 (leave blank to use 'user'): admin
メールアドレス: <省略可>
Password: admin
Password (again): admin
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.Booksアプリの作成
booksアプリのモデルを作成します。
例:$PJ/app/models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published = models.DateField()
def __str__(self):
return self.title を追加:$PJ/app/serializers.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__' に追加:$PJ/app/views.py
from rest_framework import status, viewsets
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.generic.base import TemplateView
from .models import Book
from .serializers import BookSerializer
class IndexView(TemplateView):
template_name = "index.html"
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
class SampleAPIView(APIView):
def get(self, request):
return Response("OK", status=status.HTTP_200_OK)
$PJ/app/urls.py を更新:
from rest_framework import routers
from app.views import BookViewSet, IndexViewSet
router = routers.DefaultRouter()
router.register(r'books', BookViewSet)
urlpatterns = [
path('sample/', SampleAPIView.as_view(), name='sample'),
]
urlpatterns += router.urls$PJ/project/urls.pyを更新:
from django.contrib import admin
from django.urls import path, include, re_path
from django.conf import settings
from django.conf.urls.static import static
from app.views import IndexView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('app.urls')),
re_path(r"^.*$", IndexView.as_view()),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
DBマイグレーション:
cd $PJ/
./manage.py makemigrations
./manage.py migrateデータベースの確認
データベースにテーブルやカラムがちゃんとできているか確認しておきます。sqlite3がインストールされていなければ事前にインストールしておきます。
sudo apt install sqlite3
cd $PJ
sqlite3 db.sqlite3
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite> .tables
app_book auth_user_user_permissions
auth_group django_admin_log
auth_group_permissions django_content_type
auth_permission django_migrations
auth_user django_session
auth_user_groups
sqlite> .schema app_book
CREATE TABLE IF NOT EXISTS "app_book" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(200) NOT NULL, "author" varchar(100) NOT NULL, "published" date NOT NULL);
sqlite> .quitデータの登録
一覧表示させるための書籍データを登録しておきます。
に、アクセスして以下の画面でタイトルと著者、発行年月日を何件か登録してください。

Vue3にBookViewを追加
$PJ/frontend/src/views/BooksView.vue を作成:
<template>
<v-container>
<v-card class="ma-5 pa-5">
<v-card-title>Books</v-card-title>
<v-card-text>
<v-btn color="primary" @click="callApi">Call Django API</v-btn>
<p v-if="isLoading">読み込み中...</p>
<p v-else-if="errorMessage">{{ errorMessage }}</p>
<ul v-else>
<li v-for="item in response" :key="item.id">
{{ item.id }} - {{ item.title }} - {{ item.author }} - {{ item.published }}
</li>
</ul>
</v-card-text>
</v-card>
</v-container>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
const response = ref([]);
const isLoading = ref(true);
const errorMessage = ref('');
onMounted(async () => {
try {
const res = await axios.get('//localhost:8000/api/v1/books/');
response.value = res.data;
} catch (err) {
errorMessage.value = 'データ読み込みに失敗しました';
response.value = 'Error: ' + err.message;
} finally {
isLoading.value = false;
}
});
</script>/$PJ/frontendsrc/router/index.js を以下の通り修正:
import { createRouter, createWebHistory } from 'vue-router'
import SampleView from '../views/SampleView.vue'
import BooksView from '../views/BooksView.vue'
const routes = [
{
path: '/',
name: 'Home',
component: SampleView,
},
{
path: '/books',
name: 'Books',
component: BooksView,
},
]
export default createRouter({
history: createWebHistory(),
routes,
})コードができたらビルドします。
cd $PJ/frontend
npm run build以下の通りアクセスすると登録したデータが出力されます。
http://localhost:8000/books

テーブル表示させてみる
v-data-tableを使ってテーブルを用いてデータを表示させてみます。
iconが正しく表示されるように以下の mdi/fontをインストールする
npm cache clean --force
npm install @mdi/font --save$PJ/frontend/src/main.js を以下のように修正:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify'
import '@mdi/font/css/materialdesignicons.css'
import './plugins/webfontloader';
createApp(App).use(router)
.use(router)
.use(vuetify)
.mount('#app')$PJ/frontend/src/views/BooksView.vue を以下のように編集する:
<template>
<v-container>
<div>
<v-text-field
v-model="search"
label="検索"
outlined
clearable
>
</v-text-field>
<v-data-table
:headers="headers"
v-models="selected"
:items="filteredItems"
item-value="name"
select-strategy="single"
show-select
:search="search"
>
</v-data-table>
</div>
</v-container>
</template>
<script setup>
import { ref, computed } from 'vue';
import axios from 'axios';
const isLoading = ref(true);
const errorMessage = ref('');
const search = ref('');
const items = ref([]);
const headers = [
{ title: 'ID', align: 'start', key: 'id' },
{ title: '書籍名', align: 'start', key: 'title' },
{ title: '著者', align: 'start', key: 'author' },
{ title: '発行日', align: 'start', key: 'published' },
]
const fetchData = async () => {
try {
const response = await axios.get('//localhost:8000/api/v1/books/');
items.value = response.data;
} catch (err) {
errorMessage.value = 'データ読み込みに失敗しました';
console.error(errorMessage.value);
} finally {
isLoading.value = false;
}
};
fetchData();
const filteredItems = computed(() => {
if (!search.value) {
return items.value;
}
return items.value.filter(item =>
item.name.tolowerCase().includes(search.value.tolowerCase())
);
});
</script>上記の変更が終わったらビルドします。
cd $PJ/frontend/
npm run buildブラウザでアクセスすると、以下のような感じになります。
http://localhost:8000/books

上記のVue側でフィルタをする例だとデータ数が大きくなった際に毎回DjangoからREST APIで全件取得してくることになり遅くて使えないという状況になると思います。
データ量が多くなる場合は、DjangoにQueryでフィルタ結果だけを取得するように、そしてページネーションでその結果に対してOffsetで必要数だけ取得するような作りに変える必要があると思います。
レイアウトを適用する
よく見かけるページは上段にナビゲーションバーがあり、左ペインにリスト、右ペインがのようなレイアウトにして見ます。
✅ まとめ
| 要素 | 技術 | 状況 |
|---|---|---|
| フロント | Vue3 + Vuetify | SPA構築OK |
| 通信 | Axios | Django REST Frameworkと通信 |
| バックエンド | Django + DRF + SQLite3 | REST API提供 |
| 統合 | vue.config.js の proxy 設定 + Django IndexView | SPA統合OK |
今後、Vue 側で CRUD 画面(一覧・追加・削除)を実装や、Django 側で認証(JWT / Session)対応などへ進めていってアプリケーションとして機能するものを作ってみようと思います。
