はじめに
今回はDockerとNuxt.jsとTypeScriptとExpressを使ってタスク管理アプリを作成していきます。
こんな感じのアプリです。
前回書いた記事のTypeScriptバージョンです。
JavaScriptを勉強中の方は以下の記事を見てください。
対象読者
- Dockerを使ったことがない方
- Nuxt.jsとExpressで何か作ってみたい方
- TypeScriptを使ったことがない方
- TypeScriptのORMであるPrismaを使ってみたい方
完成コード
それでは一緒に作っていきましょう!
Githubからクローン
Dockerから解説となると長くなるので省略します。
上記のファイルで環境構築が可能です。
以下の記事で具体的なやり方が書いてありますのでこちらを参考にしてください。
api/prisma/schema.prisma
を修正する
api/prisma/schema.prisma
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
+ model Task {
+ id Int @id @default(autoincrement())
+ content String
+ isFinished Boolean @default(false)
+ createdAt DateTime @default(now())
+ }
タスク追加機能の作成
フロントエンド
frontend/pages/index.vue
<template>
<div>
<h2>Task App</h2>
<br />
<br />
<h3>ADD_TASK</h3>
<br />
<input type="text" v-model="content" />
<button @click="createTask()">追加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, useAsync, useContext } from '@nuxtjs/composition-api';
import axios from '@nuxtjs/axios';
type TaskType = {
id?: number;
content: string;
isFinished: boolean;
createdAt?: string;
};
export default defineComponent({
setup() {
// axios
const { $axios } = useContext();
// data
const content = ref<string>('');
const task = ref<TaskType>({
content: content,
isFinished: false,
});
// methods
const createTask = async () => {
window.location.href = 'http://localhost:3000';
await $axios.$post('/api/tasks/store', {
task: task.value,
});
};
return {
// data
content,
task,
// methods
createTask,
};
},
});
</script>
バックエンド
app.ts
api/src/app.ts
// express
import express from 'express';
// controller_file
import indexController from './controller/IndexController';
+ import taskController from './controller/TaskController';
// app
const app = express();
// bodyParser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// cors
import cors from 'cors';
app.use(cors());
// routing
app.use('/api', indexController);
+ app.use('/api/tasks', taskController);
export default app;
TaskController.ts
api/src/controller/TaskController.ts
import { PrismaClient } from '@prisma/client';
import { Router, Request, Response } from 'express';
const prisma = new PrismaClient();
const router = Router();
// POST /api/tasks/store
router.post('/store', async (req: Request, res: Response) => {
await prisma.task.create({
data: {
content: req.body.task.content,
isFinished: req.body.task.isFinished,
},
});
});
export default router;
タスク一覧機能を作成
フロントエンド
frontend/pages/index.vue
<template>
<div>
<h2>Task App</h2>
<br />
<br />
+ <h3>SHOW_TASK</h3>
+ <br />
+ <table>
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>CONTENT</th>
+ <th>STATUS</th>
+ <th>DELETE</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="(task, index) in tasks" :key="index">
+ <td>{{ index + 1 }}</td>
+ <td>{{ task.content }}</td>
+ <td v-if="task.isFinished === true">
+ <button>完了</button>
+ </td>
+ <td v-else>
+ <button>作業中</button>
+ </td>
+ <td><button>削除</button></td>
+ </tr>
+ </tbody>
+ </table>
+ <br />
<h3>ADD_TASK</h3>
<br />
<input type="text" v-model="content" />
<button @click="createTask()">追加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, useAsync, useContext } from '@nuxtjs/composition-api';
import axios from '@nuxtjs/axios';
type TaskType = {
id?: number;
content: string;
isFinished: boolean;
createdAt?: string;
};
export default defineComponent({
setup() {
// axios
const { $axios } = useContext();
// asyncData
+ useAsync(async () => {
+ const result = await $axios.$get('/api/tasks');
+ tasks.value = result.tasks;
+ });
// data
const content = ref<string>('');
const task = ref<TaskType>({
content: content,
isFinished: false,
});
+ const tasks = ref<TaskType[]>();
// methods
const createTask = async () => {
window.location.href = 'http://localhost:3000';
await $axios.$post('/api/tasks/store', {
task: task.value,
});
};
return {
// data
content,
task,
tasks,
// methods
createTask,
};
},
});
</script>
バックエンド
api/src/controller/TaskController.ts
import { PrismaClient } from '@prisma/client';
import { Router, Request, Response } from 'express';
const prisma = new PrismaClient();
const router = Router();
+ // GET /api/tasks
+ router.get('/', async (req: Request, res: Response) => {
+ const tasks = await prisma.task.findMany();
+ res.status(200).json({ tasks });
+ });
// POST /api/tasks/store
router.post('/store', async (req: Request, res: Response) => {
await prisma.task.create({
data: {
content: req.body.task.content,
isFinished: req.body.task.isFinished,
},
});
});
export default router;
タスク削除機能を作成
フロントエンド
frontend/pages/index.vue
<template>
<div>
<h2>Task App</h2>
<br />
<br />
<h3>SHOW_TASK</h3>
<br />
<table>
<thead>
<tr>
<th>ID</th>
<th>CONTENT</th>
<th>STATUS</th>
<th>DELETE</th>
</tr>
</thead>
<tbody>
<tr v-for="(task, index) in tasks" :key="index">
<td>{{ index + 1 }}</td>
<td>{{ task.content }}</td>
<td v-if="task.isFinished === true">
<button>完了</button>
</td>
<td v-else>
<button>作業中</button>
</td>
+ <td><button @click="deleteTask(task.id, task.isFinished)">削除</button></td>
</tr>
</tbody>
</table>
<br />
<h3>ADD_TASK</h3>
<br />
<input type="text" v-model="content" />
<button @click="createTask()">追加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, useAsync, useContext } from '@nuxtjs/composition-api';
import axios from '@nuxtjs/axios';
type TaskType = {
id?: number;
content: string;
isFinished: boolean;
createdAt?: string;
};
export default defineComponent({
setup() {
// axios
const { $axios } = useContext();
// asyncData
useAsync(async () => {
const result = await $axios.$get('/api/tasks');
tasks.value = result.tasks;
});
// data
const content = ref<string>('');
const task = ref<TaskType>({
content: content,
isFinished: false,
});
const tasks = ref<TaskType[]>();
// methods
const createTask = async () => {
window.location.href = 'http://localhost:3000';
await $axios.$post('/api/tasks/store', {
task: task.value,
});
};
+ const deleteTask = async (id: number) => {
+ window.location.href = 'http://localhost:3000';
+ await $axios.$post('/api/tasks/delete', {
+ id: id,
+ });
+ };
return {
// data
content,
task,
tasks,
// methods
createTask,
+ deleteTask,
};
},
});
</script>
バックエンド
api/src/controller/TaskController.ts
import { PrismaClient } from '@prisma/client';
import { Router, Request, Response } from 'express';
const prisma = new PrismaClient();
const router = Router();
// GET /api/tasks
router.get('/', async (req: Request, res: Response) => {
const tasks = await prisma.task.findMany();
res.status(200).json({ tasks });
});
// POST /api/tasks/store
router.post('/store', async (req: Request, res: Response) => {
await prisma.task.create({
data: {
content: req.body.task.content,
isFinished: req.body.task.isFinished,
},
});
});
+ // POST /api/tasks/delete
+ router.post('/delete', async (req: Request, res: Response) => {
+ const id: number = req.body.id;
+ await prisma.task.delete({
+ where: {
+ id: id,
+ },
+ });
+ });
export default router;
タスク更新機能を作成
フロントエンド
frontend/pages/index.vue
<template>
<div>
<h2>Task App</h2>
<br />
<br />
<h3>SHOW_TASK</h3>
<br />
<table>
<thead>
<tr>
<th>ID</th>
<th>CONTENT</th>
<th>STATUS</th>
<th>DELETE</th>
</tr>
</thead>
<tbody>
<tr v-for="(task, index) in tasks" :key="index">
<td>{{ index + 1 }}</td>
<td>{{ task.content }}</td>
<td v-if="task.isFinished === true">
<button @click="updateTask(task.id, task.isFinished)">完了</button>
</td>
<td v-else>
+ <button @click="updateTask(task.id, task.isFinished)">作業中</button>
</td>
+ <td><button @click="deleteTask(task.id, task.isFinished)">削除</button></td>
</tr>
</tbody>
</table>
<br />
<h3>ADD_TASK</h3>
<br />
<input type="text" v-model="content" />
<button @click="createTask()">追加</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, useAsync, useContext } from '@nuxtjs/composition-api';
import axios from '@nuxtjs/axios';
type TaskType = {
id?: number;
content: string;
isFinished: boolean;
createdAt?: string;
};
export default defineComponent({
setup() {
// axios
const { $axios } = useContext();
// asyncData
useAsync(async () => {
const result = await $axios.$get('/api/tasks');
tasks.value = result.tasks;
});
// data
const content = ref<string>('');
const task = ref<TaskType>({
content: content,
isFinished: false,
});
const tasks = ref<TaskType[]>();
// methods
const createTask = async () => {
window.location.href = 'http://localhost:3000';
await $axios.$post('/api/tasks/store', {
task: task.value,
});
};
const deleteTask = async (id: number) => {
window.location.href = 'http://localhost:3000';
await $axios.$post('/api/tasks/delete', {
id: id,
});
};
+ const updateTask = async (id: number, isFinished: boolean) => {
+ window.location.href = 'http://localhost:3000';
+ await $axios.$post('/api/tasks/update', {
+ id: id,
+ isFinished: isFinished,
+ });
+ };
return {
// data
content,
task,
tasks,
// methods
createTask,
deleteTask,
+ updateTask,
};
},
});
</script>
バックエンド
api/src/controller/TaskController.ts
import { PrismaClient } from '@prisma/client';
import { Router, Request, Response } from 'express';
const prisma = new PrismaClient();
const router = Router();
// GET /api/tasks
router.get('/', async (req: Request, res: Response) => {
const tasks = await prisma.task.findMany();
res.status(200).json({ tasks });
});
// POST /api/tasks/store
router.post('/store', async (req: Request, res: Response) => {
await prisma.task.create({
data: {
content: req.body.task.content,
isFinished: req.body.task.isFinished,
},
});
});
// POST /api/tasks/delete
router.post('/delete', async (req: Request, res: Response) => {
const id: number = req.body.id;
await prisma.task.delete({
where: {
id: id,
},
});
});
+ // POST /api/tasks/update
+ router.post('/update', async (req: Request, res: Response) => {
+ const id: number = req.body.id;
+ const isFinished: boolean = req.body.isFinished;
+
+ await prisma.task.update({
+ where: { id: id },
+ data: { isFinished: !isFinished },
+ });
+ });
export default router;
終わりに
これで、TypeScript
とComposition-API
、Prisma
の導入ができました。
次はもう少しレベルを上げたものを作っていくことにします!