本記事の対象者
- Nuxt.jsで基礎的なアプリを作成したい方
- Nuxt.jsでTypeScriptを使いたい方
- 外部APIを使用したアプリを作成したい方
用意する環境
- Nuxt.js
- Django3.1
環境構築がまだ済んでいない方は下記を参考にしてください。


また今回はDjangoで作成したAPIを使用するので下記を参考にそちらも作成しておきます。本記事ではNuxt側のフロント部分のみを紹介しています。

Nuxt側の開発前の準備
Nuxtプロジェクトで下記コマンドを実行します。どちらもVueでTypeScriptを使用するために必要なモジュールになります。
$ yarn add vue-class-component $ yarn add vue-property-decorator
インストールが完了するとpackage.jsonに記述が追加されているはずですので確認してみてください。
次に.envファイルをプロジェクト直下に作成します。APIのURLを設定します。
# .env
API_URL = http://localhost:8000/api
nuxt.config.jsをnuxt.config.tsに修正します。
// nuxt.config.ts import colors from 'vuetify/es5/util/colors' const NuxtConfig = { // Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode ssr: false, // Target: https://go.nuxtjs.dev/config-target target: 'static', // Global page headers: https://go.nuxtjs.dev/config-head head: { titleTemplate: '%s - docker_nuxt', title: 'docker_nuxt', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1', }, { hid: 'description', name: 'description', content: '' }, ], link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], }, // Global CSS: https://go.nuxtjs.dev/config-css css: [], // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins plugins: [], // Auto import components: https://go.nuxtjs.dev/config-components components: true, // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules buildModules: [ // https://go.nuxtjs.dev/typescript '@nuxt/typescript-build', // https://go.nuxtjs.dev/vuetify '@nuxtjs/vuetify', ], // Modules: https://go.nuxtjs.dev/config-modules modules: [ // https://go.nuxtjs.dev/axios '@nuxtjs/axios', // https://go.nuxtjs.dev/pwa '@nuxtjs/pwa', ], // Axios module configuration: https://go.nuxtjs.dev/config-axios axios: {}, // PWA module configuration: https://go.nuxtjs.dev/pwa pwa: { manifest: { lang: 'ja', }, }, // Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify vuetify: { customVariables: ['~/assets/variables.scss'], theme: { dark: true, themes: { dark: { primary: colors.blue.darken2, accent: colors.grey.darken3, secondary: colors.amber.darken3, info: colors.teal.lighten1, warning: colors.amber.base, error: colors.deepOrange.accent4, success: colors.green.accent3, }, }, }, }, // Build Configuration: htts://go.nuxtjs.dev/config-build build: {}, publicRuntimeConfig: { apiURL: process.env.API_URL, }, } export default NuxtConfig
作成する画面と機能
- TodoList画面
- 機能
- 未完了のタスク一覧を表示する機能
- タスクを削除する機能
- タスクを完了にする機能
- タスク編集画面に遷移する機能
- 新規タスクを作成する機能
- 機能
- Todo完了List画面
- 機能
- 完了のタスク一覧を表示する機能
- タスクを削除する機能
- タスクを未完了にする機能
- 機能
- Todo編集画面
- 機能
- 既存のタスクを編集する機能
- 機能
こんな感じで画面を作成していきます。
図にするとこんな関係性になります。
コンポーネントの作成
まずはコンポーネントを作成していきます。
components配下にForm.vueというファイルを作成します。
こちらはファイル名からもわかりそうですが、タスクを作成するformになります。
TodoList画面の新規タスク作成とTodo編集画面の既存のタスクを編集する際に使用します。
下記がソースです。
// Form.vue <template> <v-container> <v-row> <v-card class="d-inline-block mx-auto text-center"> <v-form> <v-container> <v-row> <v-col cols="12" sm="12"> <v-text-field v-model="todo" label="やること" :error="errors.todo.flag" :error_count="errors.todo.count" :messages="errors.todo.message" clearable ></v-text-field> </v-col> <v-col cols="12" sm="6"> <v-select v-model="selectedPriority" label="優先度" dense :items="priority" :error="errors.priority.flag" :error_count="errors.priority.count" :messages="errors.priority.message" item-text="label" item-value="value" ></v-select> </v-col> <v-col cols="12" sm="6" class="text-right"> <v-btn class="mr-4" type="button" @click="submit()"> submit </v-btn> </v-col> </v-row> </v-container> </v-form> </v-card> </v-row> </v-container> </template> <script lang="ts"> import { Component, Vue, Prop, Watch, Emit } from 'vue-property-decorator' import axios from 'axios' interface Priority { label: string value: number } interface ErrorInfo { flag: boolean count: number message: string[] } interface ErrorInfoes { [key: string]: ErrorInfo } interface ErrorResponse { [key: string]: string[] } @Component export default class FormVue extends Vue { @Prop({ type: Number, required: false }) initPriority!: number @Prop({ type: String, required: false }) initTodo!: string @Prop({ type: String, required: false }) paramId?: string selectedPriority?: number | null = null labelList: string[] = ['todo', 'priority'] todo?: string = '' errors: ErrorInfoes = { todo: { flag: false, count: 0, message: [], }, priority: { flag: false, count: 0, message: [], }, } priority: Priority[] = [ { label: '小', value: 0 }, { label: '中', value: 1 }, { label: '大', value: 2 }, ] @Emit() public initTodoList() {} // 親のgetTodo()を実行する要素として用意 submit(): void { this.initErrorInfo() if (this.paramId) { this.putSubmit(this.paramId) } else { this.postSubmit() } } postSubmit(): void { try { axios .post(process.env.API + '/tasks/', { todo: this.todo, priority: this.selectedPriority, }) .then(() => { // リストとformの値を初期化 this.initFormValue() this.initTodoList() }) .catch((error) => { const errorList = error.response.data this.displayError(errorList) }) } catch (e) { return e } } putSubmit(id: string): void { try { axios .put(process.env.API + '/tasks/' + id + '/', { todo: this.todo, priority: this.selectedPriority, }) .then(() => { // リストとformの値を初期化 this.initFormValue() this.initTodoList() }) .catch((error) => { const errorList = error.response.data this.displayError(errorList) }) } catch (e) { return e } } initErrorInfo(): void { for (const key of this.labelList) { this.errors[key].flag = false this.errors[key].count = 0 this.errors[key].message = [] } } initFormValue() { this.selectedPriority = null this.todo = '' } displayError(errorList: ErrorResponse): void { for (const key in errorList) { this.errors[key].count = errorList[key].length this.errors[key].message = errorList[key] this.errors[key].flag = true } } @Watch('initPriority') onChangeInitPriority(newVal: number): void { this.selectedPriority = newVal } @Watch('initTodo') onChangeInitTodo(newVal: string): void { this.todo = newVal } } </script>
次にcomponents配下にTaskList.vueというファイルを作成します。
こちらのコンポーネントは作成したタスク一覧を表示するに使用します。
TodoList画面とTodo完了List画面に使用します。
<template> <div> <template v-if="!completeFlag"> <FormVue @init-todo-list="getTodoList()" /> </template> <v-container fluid> <v-simple-table> <thead> <tr> <th class="text-left">ID</th> <th class="text-left">やること</th> <th class="text-left">優先度</th> <th class="text-center">ステータス更新</th> <!-- 未完了画面のみ --> <th v-if="!completeFlag" class="text-center">編集</th> <th class="text-center">削除</th> </tr> </thead> <tbody> <tr v-for="task in taskList" :key="task.id"> <td>{{ task.id }}</td> <td>{{ task.todo }}</td> <td>{{ stringPriority(task.priority) }}</td> <td v-if="!completeFlag" class="text-center"> <v-btn class="ma-2" color="primary" dark @click="completeClick(task.id)" > Complete <v-icon dark right> mdi-checkbox-marked-circle </v-icon> </v-btn> </td> <td v-else class="text-center"> <v-btn class="ma-2" color="secondary" dark @click="completeClick(task.id)" > Incomplete <v-icon dark right> mdi-checkbox-marked-circle </v-icon> </v-btn> </td> <!-- 未完了画面のみ --> <td v-if="!completeFlag" class="text-center"> <v-btn :to="'/task/' + task.id" class="ma-2" color="green" dark> Edit <v-icon dark right> mdi-file-document-edit </v-icon> </v-btn> </td> <td class="text-center"> <v-btn class="ma-2" color="red" dark @click="deleteClick(task.id)" > Delete <v-icon dark right>mdi-cancel</v-icon> </v-btn> </td> </tr> </tbody> </v-simple-table> </v-container> </div> </template> <script lang="ts"> import { Component, Vue, Prop } from 'vue-property-decorator' import axios from 'axios' import FormVue from '~/components/Form.vue' interface Task { id: number todo: string priority: number completeFlag: boolean } interface Priority { [key: number]: string } axios.defaults.xsrfCookieName = 'csrftoken' axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN' @Component({ components: { FormVue, }, }) export default class TaskList extends Vue { taskList: Task[] = [] priority: Priority = { 0: '小', 1: '中', 2: '大' } @Prop({ type: Boolean, required: true }) completeFlag!: boolean created() { this.getTodoList() } getTodoList() { try { axios .get(process.env.API + '/tasks/', { responseType: 'json', params: { complete_flag: this.completeFlag }, }) .then((response) => { this.taskList = response.data }) .catch((error) => { return error }) } catch (e) { return e } } completeClick(id: string) { try { axios .patch(process.env.API + '/tasks/' + id + '/', { complete_flag: !this.completeFlag, }) .then(() => { // リストを初期化 this.getTodoList() }) } catch (e) { return e } } deleteClick(id: string) { try { axios.delete(process.env.API + '/tasks/' + id + '/').then(() => { // リストを初期化 this.getTodoList() }) } catch (e) { return e } } stringPriority(priority: number): string { return this.priority[priority] } } </script>
これでコンポーネントの作成は完了です。
TodoList画面の作成
まずはtaskディレクトリを作成します。その後taskディレクトリ配下にindex.vueというファイルを作成します。
このファイルがTodoList画面のテンプレートファイルになります。
このindex.vueで先ほど作成したForm.vueとTaskList.vueコンポーネントをインポートしていきます。具体的なソースは下記になります。
// index.vue <template> <div> <h2>Task List</h2> <TaskList :complete-flag="myCompleteFlag" /> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import TaskList from '~/components/TaskList.vue' @Component({ components: { TaskList, }, }) export default class IndexVue extends Vue { myCompleteFlag: boolean = false } </script>
では http://localhost:9000/task アクセスしてみましょう。

Djangoを起動していればAPIでデータの作成が可能ですのでフォームに入力してみましょう。
うまくいくとデータがフォームの下の表示されます。

Todo完了List画面の作成
機能としては完了したタスクを表示するのみを想定しているのでTaskList.vueコンポーネントのみをインポートしていきます。
具体的なソースは下記になります。
// complete.vue <template> <div> <h2>Task List Complete</h2> <TaskList :complete-flag="myCompleteFlag" /> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import TaskList from '~/components/TaskList.vue' @Component({ components: { TaskList, }, }) export default class CompleteVue extends Vue { myCompleteFlag: boolean = true } </script>
では http://localhost:9000/task/complete アクセスしてみましょう。

Todo編集画面
taskディレクトリ配下に_id.vueを作成します。アンダースコアをつけると動的にURLとなります。
/task/1 や/task/10 のようなURLを想定しています。この1や10といったパラメータを使用してどのタスクをAPIで取得するかを判断する仕様です。
既存のデータを修正するフォームが必要なので、この画面ではForm.vueコンポーネントをインポートしていきます。
具体的なソースは下記になります。
// _id.vue <template> <div> <h2>Task Edit - ID {{ $route.params.id }}</h2> <FormVue :init-priority="task.priority" :init-todo="task.todo" :param-id="$route.params.id" @init-todo-list="redirectTodo()" /> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import axios from 'axios' import FormVue from '~/components/Form.vue' axios.defaults.xsrfCookieName = 'csrftoken' axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN' interface Task { id: Number | null todo: String | '' priority: Number | null completeFlag: Boolean | null } @Component({ components: { FormVue, }, validate({ params }) { // 数値以外は404にする return /^\d+$/.test(params.id) }, }) export default class IdVue extends Vue { task?: Task = { id: null, todo: '', priority: null, completeFlag: null, } created() { this.getTodo() } getTodo() { try { axios .get(this.$config.apiURL + '/tasks/' + this.$route.params.id + '/', { responseType: 'json', }) .then((response) => { this.task = response.data }) .catch((error) => { return error }) } catch (e) { return e } } redirectTodo() { this.$router.push('/task') } } </script>
TaskList画面で作成したデータのeditボタンをクリックしてみると下記のような画像が表示されます。

コメント