📆

カレンダーアプリを作る(バックエンド編)

に公開

概要

フルスタックなアプリケーション開発の練習として、カレンダーアプリを作成することにしました。
Googleカレンダーを参考にしながら作成しています。

フロントエンドについては、別の記事にしてあるので、そちらをご覧ください。
今回はバックエンドの開発をしていきます。

技術スタック

アーキテクチャ構成
BackEnd-API: NestJS
DB: MySQL
ORM: Prisma

機能要件

  • スケジュールをDBに登録できるAPI:
    このAPIは、ユーザーが新しいスケジュールをデータベースに追加するためのエンドポイントです。リクエストには、スケジュールの詳細(タイトル、開始日時、終了日時、終日など)が含まれます。

  • 全てのスケジュールをDBから取得するAPI:
    このAPIは、データベースに保存されているすべてのスケジュールを取得するためのエンドポイントです。リクエストを送信すると、全スケジュールのリストがJSON形式で返されます。

  • 指定したスケジュールをDBから削除するAPI:
    このAPIは、特定のスケジュールをデータベースから削除するためのエンドポイントです。リクエストには削除したいスケジュールのIDが含まれます。

  • 指定したスケジュールを更新するAPI:
    このAPIは、既存のスケジュールの情報を更新するためのエンドポイントです。リクエストには更新したいスケジュールのIDと新しい情報が含まれます。

テーブル設計

カレンダーアプリでは、スケジュールテーブルとユーザーテーブルを作成しました。
一応ユーザーテーブルを作っていますが、今回は使いません。

なおこのアプリケーションでは、ORMとしてPrismaを使用しており、以下のようにスキーマを定義しています。
ORMを使うことにより、データベースのテーブルとプログラム内のオブジェクトをマッピングすることができます。これにより、データベースの行がオブジェクトとして扱われ、属性がオブジェクトのプロパティとして表現されます。その結果、CRUD操作をオブジェクト指向の方法で行うことができます。(複雑なSQLを書かなくても良くなる)

schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  password  String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("users")
}

model Schedule {
  id        Int      @id @default(autoincrement())
  title     String
  allDay    Boolean
  start     DateTime
  end       DateTime
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

環境構築

環境構築については、下記の記事を参考に作成しました。
そのため、ここでの詳しい説明は省略します。
https://qiita.com/Yosuke_Narumi/items/5dd9225bb71d30b1890f#5-prismaを利用してmysqlと接続する

実装

Nest.jsの概要ついては、下記の記事が参考になりました。
https://zenn.dev/morinokami/articles/nestjs-overview#controllers

Controllerの作成

下記コマンドを実行し、schedulesコントローラーを作成します。

% docker-compose run nest nest g controller schedules

APIをそれぞれ作成していきます。

/src/schedules/schedules.controller.ts
import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
} from '@nestjs/common';
import { Schedule } from '@prisma/client';
import { SchedulesService } from './schedules.service';

@Controller('schedules')
export class SchedulesController {
  constructor(private readonly schedulesService: SchedulesService) {}

  // 全てのスケジュールを取得するエンドポイント
  @Get()
  async schedules(): Promise<Schedule[]> {
    return this.schedulesService.schedules();
  }

  // 新しいスケジュールを作成するエンドポイント
  @Post()
  async createSchedule(
    @Body()
    scheduleData: {
      title: string;
      start: Date;
      end: Date;
      allDay: boolean;
    },
  ): Promise<Schedule> {
    return this.schedulesService.createSchedule(scheduleData);
  }

  // 指定したIDのスケジュールを削除するエンドポイント
  @Delete(':id')
  async deleteSchedule(@Param('id') id: string): Promise<Schedule> {
    return this.schedulesService.deleteSchedule(Number(id));
  }

  // 指定したIDのスケジュールを新しい内容で更新するエンドポイント
  @Patch(':id')
  async updateSchedule(
    @Param('id') id: string,
    @Body()
    scheduleData: {
      title: string;
      start: Date;
      end: Date;
      allDay: boolean;
    },
  ): Promise<Schedule> {
    return this.schedulesService.updateSchedule(Number(id), scheduleData);
  }
}

Providerの作成

下記コマンドを実行し、schedulesサービスを作成します。

% docker-compose run nest nest generate service scheudles

各CRUD処理を記載していきます。
createScheduleについては、受け取ったデータの中身がstring型になっているため、それぞれ、Date型、Boolean型に直してからcreateにデータを入れてあげます。

/src/schedules/schedules.service.ts
import { Injectable } from '@nestjs/common';
import { Prisma, Schedule } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';

@Injectable()
export class SchedulesService {
  constructor(private prisma: PrismaService) {}

  async schedules(): Promise<Schedule[]> {
    return this.prisma.schedule.findMany();
  }

  async createSchedule(data: Prisma.ScheduleCreateInput): Promise<Schedule> {
    data.start = new Date(data.start);
    data.end = new Date(data.end);
    data.allDay = Boolean(data.allDay);
    return this.prisma.schedule.create({
      data,
    });
  }

  async deleteSchedule(id: number): Promise<Schedule> {
    return this.prisma.schedule.delete({
      where: { id },
    });
  }

  async updateSchedule(
    id: number,
    data: Prisma.ScheduleUpdateInput,
  ): Promise<Schedule> {
    if (typeof data.start === 'string' || data.start instanceof Date) {
      data.start = new Date(data.start);
    }
    if (typeof data.end === 'string' || data.end instanceof Date) {
      data.end = new Date(data.end);
    }
    data.allDay = Boolean(data.allDay);
    return this.prisma.schedule.update({
      where: { id },
      data,
    });
  }
}

Modulesの作成

% docker-compose run nest nest g module schedules

コントローラーとプロバイダーを登録します。

/src/schedules/schedules.module.ts
import { Module } from '@nestjs/common';
import { SchedulesController } from './schedules.controller';
import { SchedulesService } from './schedules.service';
import { PrismaService } from 'src/prisma/prisma.service';

@Module({
  controllers: [SchedulesController],
  providers: [SchedulesService, PrismaService],
})
export class SchedulesModule {}

curlコマンドで各APIの確認を行います。

% curl -H "content-type: application/json" -X POST -d'{"title":"test", "start":"2020-02-02T19:05:06.000Z", "end":"2020-02-03T19:05:06.000Z", "allDay":true}' http://localhost:3000/schedules

% curl -X GET http://localhost:3000/schedules

% curl -X DELETE http://localhost:3000/schedules/1

% curl -H "content-type: application/json" -X PATCH -d'{"title":"aaaaa", "start":"2020-02-02T19:05:06.00Z", "end":"2020-02-03T19:05:06.000Z", "allDay":true}' http://localhost:3000/schedules/1

まとめ

カレンダーアプリのバックエンドをNest.js、Prisma、MySQLを使用して構築しました。Nest.jsのおかげで、非常に簡単にAPIを作成でき、Prismaを使うことで複雑なSQLを書く必要がなくなり、バックエンド構築のハードルが大幅に下がりました。
今回は簡単なアプリのため、テーブル構造もシンプルですが、今後、より規模の大きいアプリを作成する際には、今回学んだことを活かしてバックエンドを構築していくつもりです。

なお、フロントエンドとバックエンドの繋ぎこみについては、「カレンダーアプリを作る(フロントエンド編④)」で書いております。

Create Quest.Inc Tech Blog

Discussion