1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Docker / Nuxt.js / TypeScript / Expressで作るタスク管理アプリ】PrismaでCRUDをやってみよう!

Last updated at Posted at 2021-08-30

はじめに

今回はDockerとNuxt.jsとTypeScriptとExpressを使ってタスク管理アプリを作成していきます。
こんな感じのアプリです。

スクリーンショット 2021-08-30 18.32.01.png

前回書いた記事の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;

終わりに

これで、TypeScriptComposition-APIPrismaの導入ができました。
次はもう少しレベルを上げたものを作っていくことにします!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?