🙆‍♀️

3章 フロントエンド-バックエンド-DBの疎通確認。簡単なCreate、Getを実装する

に公開

概要

Next.js、Nest.js、MySQLで作成したアプリケーションの疎通確認の一環で、フロントエンドからバックエンドを通して、DBにデータの保存、取得が出来るようにする。

過去の記事はこちら

https://zenn.dev/delta_tsuruta/articles/6b0eb34f5e3805
https://zenn.dev/delta_tsuruta/articles/7701155ef89dbf

※ 注意書き

  • 疎通確認の一環なので超最低限なことしかしない
  • ディレクトリ構成は気にせずに作成するため、好きにやっていただきたい
  • 詳しい解説はしないため、気になる部分は公式ドキュメントを確認いただきたい
    https://docs.nestjs.com/

リソースの作成

バックエンド:Nest.js

generateコマンドで一通りのファイルを作成

nest.jsでは、generate(g)コマンドを実行するとファイルを自動で作成してくれる。
nest g resource リソース名

# nest g resource user

API仕様を聞かれる。今回はREST APIを選択する。

❯ REST API
  GraphQL (code first)
  GraphQL (schema first)
  Microservice (non-HTTP)
  WebSockets

CRUDを作成するかを聞かれる。

? Would you like to generate CRUD entry points? (Y/n)

yを入力すると、以下のファイルが作成される。

CREATE src/user/user.controller.spec.ts (556 bytes)
CREATE src/user/user.controller.ts (883 bytes)
CREATE src/user/user.module.ts (241 bytes)
CREATE src/user/user.service.spec.ts (446 bytes)
CREATE src/user/user.service.ts (607 bytes)
CREATE src/user/dto/create-user.dto.ts (30 bytes)
CREATE src/user/dto/update-user.dto.ts (169 bytes)
CREATE src/user/entities/user.entity.ts (21 bytes)
UPDATE package.json (2119 bytes)
UPDATE src/app.module.ts (308 bytes)

各ファイルを覗いてみると、よしなにエンドポイントを記述してくれている。
さらにapp.module.tsを確認すると勝手にmoduleをインポートしてくれている。

app.module.ts
 import { Module } from '@nestjs/common';
 import { AppController } from './app.controller';
 import { AppService } from './app.service';
+import { UserModule } from './user/user.module';

 @Module({
-  imports: [],
+  imports: [UserModule],
   controllers: [AppController],
   providers: [AppService],
 })
 export class AppModule {}

これで、何もしなくてもエンドポイントとして利用できる状態となっている。
サーバーを起動してcurlコマンドを使用すると、コマンドで作成されたserviceクラスのfindAll()メソッドの、次の結果がレスポンスとして得られる。

$ curl -X GET http://localhost:3001/user
This action returns all user

DTOの作成

リクエストパラメータとして受け取るための、DTO(Data Transfer Object)を作成する。
dtoディレクトリは先ほどのgenerateコマンドで作成されているため、そちらを使用する。
src/user/dto配下に、create-user.dto.tsが作成されているので、次のように記述する。

create-user.dto.ts
export class CreateUserDto {
  readonly username: string;
  readonly email: string;
}

PrismaClientの作成

src配下にprisma.service.tsを作成する。
Prismaを使用してデータベースへアクセスする際に必要となるクラスである。

prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

リポジトリクラスの作成

最低限の検証ができればよいので、PrismaServiceクラスを使用して、データを作成、取得するためのメソッドのみを記述する。

user.repository.ts
import { Injectable } from "@nestjs/common";
import { PrismaService } from "src/prisma.service";
import { User, Prisma } from "@prisma/client";

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

  async createUser(data: Prisma.UserCreateInput): Promise<User> {
    return this.prisma.user.create({ data });
  }
  
  async user(): Promise<User[] | null> {
    return this.prisma.user.findMany();
  }
}

Prisma.UserCreateInputはschema.prismaをDBに反映した際に、自動で作成される。

  export type UserCreateInput = {
    email: string
    name?: string | null
  }

Dockerで名前付きボリュームにしている場合、実際のファイルはコンテナ内にしかないため、確認したい場合はプロジェクト内のnode_moduleに吸い出すことで確認が可能。

$ docker cp -a コンテナ名:/app/node_modules ./
例)
$ docker cp -a backend:/app/node_modules ./

サービスクラスからリポジトリクラスを使用する

サービスクラスでリポジトリクラスを呼び出す。

user.service.ts
@Injectable()
export class UserService {
  constructor(private userRepository: UserRepository) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    return this.userRepository.createUser({
      name: createUserDto.username,
      email: createUserDto.email,
    });
  }

  async findAll(): Promise<User[] | null> {
    return this.userRepository.user();
  }

依存関係の解決

providersに必要な依存関係を記述する。

user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserRepository } from './user.repository';
import { PrismaService } from '../prisma.service';

@Module({
  controllers: [UserController],
  providers: [UserService, UserRepository, PrismaService],
})
export class UserModule {}

フロントエンド:Next.js

testFormを作成する。
前回作成したtestButton.tsxを修正するか、新しく作成する。

testFormのソースコード
testForm.tsx

import Image from "next/image";
import TestForm from "./testForm";

export default function Home() {
  return (
    <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
        <Image
          className="dark:invert"
          src="/next.svg"
          alt="Next.js logo"
          width={180}
          height={38}
          priority
        />
        <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
          <li className="mb-2">
            Get started by editing{" "}
            <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
              src/app/page.tsx
            </code>
            .
          </li>
          <li>Save and see your changes instantly.</li>
        </ol>

        <div className="flex gap-4 items-center flex-col sm:flex-row">
          <TestForm />
        </div>
      </main>
      <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
        <a
          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
          href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          <Image
            aria-hidden
            src="/file.svg"
            alt="File icon"
            width={16}
            height={16}
          />
          Learn
        </a>
        <a
          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
          href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          <Image
            aria-hidden
            src="/window.svg"
            alt="Window icon"
            width={16}
            height={16}
          />
          Examples
        </a>
        <a
          className="flex items-center gap-2 hover:underline hover:underline-offset-4"
          href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          <Image
            aria-hidden
            src="/globe.svg"
            alt="Globe icon"
            width={16}
            height={16}
          />
          Go to nextjs.org →
        </a>
      </footer>
    </div>
  );
}

page.tsxでtestFormを配置すると、次のようなUIになる。

登録、データ取得ボタンが正常に動作すれば、完了。


by 株式会社DELTA
https://teamdelta.jp/


[CTO Booster]
https://costcut.cloud/

[VersionUp booster]
https://teamdelta.jp/lp/versionup


Discussion