0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue.js(Vue3)とTypescriptで日記アプリを開発する。(①バックエンド開発ver)

Last updated at Posted at 2025-06-02

APIを開発する理由・意義

・フロントエンドとバックエンドの分離
・フロントエンドエンジニアはリッチなUI、バックエンドエンジニアはビジネスロジックやデータ管理に集中できる。
・マルチPF対応(再開発が不要)
・拡張機能などスケーラビリティに対応
・チーム開発(大規模)

JSON / XML / YAML

・JSONはXMLに比べ軽量
・JSONはキーペアの構成で可読性が高い
・JSONはJSとの親和性が高い

・XMLはJSONの前に広く使用されていた。

・YAMLはAPI使用記述などで使用される。設定ファイルとか。

READMEとYAMLの違い

→README:取扱説明書。
→YAML:プログラムやデータについて、どういう設定・構造になっているか、設定情報やデータそのものを記述するフォーマット。

・YAMLの使い方
→設定ファイル(Ruby on Rails、Django、Spring Bootなど)で、データベースの接続情報、ポート番号、外部サービスのAPIキー、ログレベル
例)config/database.yml

# 開発環境のデータベース設定
development:
  adapter: postgresql
  encoding: unicode
  database: my_app_development
  pool: 5
  username: my_user
  password: my_password
  host: localhost
  port: 5432

# 本番環境のデータベース設定
production:
  adapter: postgresql
  encoding: unicode
  database: my_app_production
  pool: 20
  username: prod_user
  password: <%= ENV['DATABASE_PASSWORD'] %> # 環境変数から読み込む例
  host: db.example.com
  port: 5432

  

→コンテナオーケストレーション (Docker Compose, Kubernetes)、コンテナ(Webサーバー、データベース、キャッシュサーバーなど)をまとめて定義し連携。

例)docker-compose.yml

version: '3.8' # YAMLファイルのバージョン指定 (オプション)

services:
  web:
    image: nginx:latest # 使用するDockerイメージ
    ports:
      - "80:80"         # ホストの80番ポートをコンテナの80番ポートにマッピング
    volumes:
      - ./html:/usr/share/nginx/html # ホストのhtmlディレクトリをコンテナのディレクトリにマウント
    depends_on:
      - app             # appサービスが起動してからwebサービスを起動

  app:
    build: .            # Dockerfileがあるディレクトリを指定してビルド
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgres://my_user:my_password@db:5432/my_app_development
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: my_user
      POSTGRES_PASSWORD: my_password
      POSTGRES_DB: my_app_development
    volumes:
      - db_data:/var/lib/postgresql/data

volumes: # 名前付きボリュームの定義
  db_data:

  

→CI/CDパイプライン (GitHub Actions, GitLab CI/CD)、ビルド、テスト、デプロイといった一連のプロセスを自動化するための手順

CI/CD

・CI:継続的統合(バージョン管理、自動ビルド・テスト)

・CD:継続的デリバリー(CIをクリアしたSWを本番リリース)
→GitHub Actions、GitLab CI/CDは幾分か無料

APIは何を使って作る?

Laravel, Python Fast API, Java Spring Boot, Node.js NestJS

今回は、Node.js NestJS、Vue + TypeScriptで日記アプリを開発、AWSにデプロイ

・NestJSはNode.js上で動作するサーバサイドアプリケーションフレームワーク。TSベースで書かれていて、API・サーバアプリを簡潔にかける。
・TSは、JS+型。
string, number, booleanなど明示できる。
コンパイル(ts→js変換)時に型エラーのチェックをするため、バグを発見しやすい。(VS Codeなら型が指定されてないよとかエラーを知らせてくれる)クラスベースのため、オブジェクト指向。(クラスは設計図、オブジェクトは実体)

★開発工程

STEP 1:NestJS プロジェクト作成とAPIの構築

(sudo) npm i -g @nestjs/cli
nest new diary-api

・NestJSのCLIをインストール(nom / yarn /pnpmのうち、pnpmを選択)
(インストールで、下記などnest〜から始まるコマンドが使用可能)

nest new <project-name>       # 新しいNestJSプロジェクトを作る
nest g controller <name>      # コントローラーを生成
nest g service <name>         # サービスを生成

npm / yarn /pnpmの違い

比較項目 npm (v9〜) yarn (v1, v3) pnpm
速度 普通(v7以降改善) v1:高速、v3:P'n'Pでさらに高速 非常に高速(最速)
node_modules管理 フラット(デフォルト) v1:フラット、v3:Plug’n’Play 厳密な構造で依存関係ミスを防ぐ
ディスク使用量 高め 普通 少ない(共有キャッシュ)
インストール整合性 v7〜は package-lock.json を重視 yarn.lock pnpm-lock.yaml
lockファイル package-lock.json yarn.lock pnpm-lock.yaml
ワークスペース対応 あり(v7〜) v1, v3ともに強い 非常に優秀(monorepo向け)
セキュリティ npm audit yarn audit pnpm audit(npmベース)
公式互換性 100% 若干独自構文あり(特にv3) ほぼnpm互換だが内部構造は異なる

STEP2:初期化

nest new diary-api

STEP3:作業ディレクトリに移動

cd dairy-api
pnpm run start()

http://localhost:3000/ にアクセスすると、初期設定の AppController により "Hello World!" が返る。

STEP4:日記用のエンティティを作成

pnpm nest g resource diary-entry

・(/dairy-entry/diary-apiにて行う。)

[選択]

・REST API → 今回はこれを選択
・Would you like to generate CRUD entry points? → Yes (後から変更可)

→以下が自動生成される。
Ldiary-entry.controller.ts
Ldiary-entry.service.ts
Ldiary-entry.module.ts
Lcreate-diary-entry.dto.ts など

[ファイルの役割・概要]
src/
diary-entry/
diary-entry.controller.ts // APIルートの定義(GET, POSTなど)
diary-entry.service.ts // ビジネスロジック
diary-entry.module.ts // モジュール定義
dto/
create-diary-entry.dto.ts // POST用の型定義
update-diary-entry.dto.ts // PUT用の型定義
entities/
diary-entry.entity.ts // DBエンティティ定義(TypeORM想定)

[選択肢が、今回の用途に向いているかチェック]

✅REST API:一般的なWeb API(HTTP + JSON)を実装する標準→◎今回最適
・GraphQL (code/schema first):フロントで必要なデータを柔軟に取得したいときに有用(複雑なUI向け) →△ 学習コストが高い
・Microservice:KafkaやMQなど非HTTPでサービス間通信する設計→✕ 今回は不要
・WebSockets:チャットやリアルタイム更新など、双方向通信が必要な場合→✕ 今回は不要

REST APIとは

・アーキテクチャスタイル
・6つの原則
Lクライアントサーバ分離
Lステートレス
Lキャッシュ可能性
L統一IF
L階層化システム
Lコードオンデマンド

・クライアントは、HTTPメソッドを使用して、URIに対してリクエストを送信
→エンドポイントでこれを行う。送信と応答を返す

・クライアント
→API送信をする側。

URLはURIの一種

・URIは識別子(レストランの住所)そのもの
・エンドポイント(そのレストランの入り口)はURIで識別された、API機能へのアクセスポイント。
https://api.example.com/products/123
https://api.example.com がAPIのベースアドレス
・「/products/123」がパス。
・全体がエンドポイント
→URI≠エンドポイント

Yes を選んだ場合に生成

・NestJSが以下のCRUD操作を自動で作成
メソッド エンドポイント 用途
GET /diary-entry 全件取得
GET /diary-entry/:id 特定ID取得
POST /diary-entry 新規作成
PATCH /diary-entry/:id 一部更新
DELETE /diary-entry/:id 削除
→自動でルーティングとコントローラ、DTOが生成される

・DTOとは

レスポンスデータの構造を明確化
バリデーションをかけ、保守性を高めることができる(class-validatorを使えば)
(DTOがないと型なしになる。dto.date, dto.timeとかができない。補完が効かずバグの温床になる。)

エンティティとは

DBと直接対応する実データ構造の定義
TypeORM(Object Relational Mapping)で必要な構造
DB1レコード、1エンティティのインスタンス

・処理の流れ

DTO→Service(メモリに保存)→ControllerでAPI公開

STEP5:Githubと連携

・githubにリポジトリを作成(diary-api)

# 1. Gitリポジトリを初期化(既に init 済ならスキップ可)
$ git init
# 2. 変更を追加して初回コミット
$ git add .
$ git commit -m "Initial commit"
# 3. GitHubリポジトリをリモートに追加(URLはあなたのリポジトリに置き換えて)
$ git remote add origin https://github.com/Toma-0205/diary-api.git
# 4. ブランチ名をmainに統一
$ git branch -M main
# 5. 初回Push
$ git push -u origin main
# 6 ブランチを分ける
$ git checkout -b dev(ローカルにdevブランチ作成)
$ git push -u origin dev(リモートにもdevを作成、ローカルと紐付け)

STEP6:日記を key + value 形式で保存・取得する APIの構築、動作確認(テスト)

・create-diary-entry.dto.ts
export class CreateDiaryEntryDto {
  key: string;    // 例: "sleepTime"
  value: string;  // 例: "23:30"
}

→POSTリクエストで送られてくるデータの「型」を定義
@Body() デコレーター(注釈)は、HTTPリクエストのBodyに含まれるJSONを受け取るためのNestJSの注釈
→マッピング、JSONの中身をDTOクラスのフィールドに対応づける

・diary-entry.service.ts
@Injectable()
export class DiaryEntryService {
  private entries: DiaryEntry[] = [];
  private idCounter = 1;

  create(entryDto: CreateDiaryEntryDto): DiaryEntry {
    const newEntry: DiaryEntry = {
      id: this.idCounter++,
      key: entryDto.key,
      value: entryDto.value,
      createdAt: new Date(),
    };
    this.entries.push(newEntry);
    return newEntry;
  }

  findAll(): DiaryEntry[] {
    return this.entries;
  }
}

→データの作成・取得・更新などを行う。

処理の流れ(create)
  1. Controllerから entryDto(key/valueのデータ)を受け取る
  2. 自動的に id をインクリメント(1, 2, 3…)
  3. createdAt に現在の日時を付与
  4. それを entries[] 配列に保存
  5. 登録されたデータを返す(findAll())
・diary-entry.controller.ts
@Controller('diary-entry')
export class DiaryEntryController {
  constructor(private readonly diaryEntryService: DiaryEntryService) {}

  @Post()
  create(@Body() dto: CreateDiaryEntryDto) {
    return this.diaryEntryService.create(dto);
  }

  @Get()
  findAll() {
    return this.diaryEntryService.findAll();
  }
}
処理の流れ

@Post()

  • POST /diary-entry にリクエストが来ると発火
  • @Body() で送られてきたJSONをDTOに変換して受け取る
  • Serviceの create() に渡す → 保存された結果を返す(Node.jsのプロセスメモリ、RAM上に一時保存、再起動すると消える)

@Get()

  • GET /diary-entry にアクセスがあると findAll() を呼び出し
  • Serviceの findAll() の戻り値をそのまま返す
  • NestJSAPI(POST /diary-entry)にデータ送信
curl -X POST http://localhost:3000/diary-entry \
  -H "Content-Type: application/json" \
  -d '{"key": "sleepTime", "value": "23:45"}'

レスポンス

curl http://localhost:3000/diary-entry
[{"id":1,"key":"sleepTime","value":"23:45","createdAt":"2025-05-20T08:17:23.407Z"}]%

→データの保存、取得ができていることが確認できる。

★処理の順番

① curlでPOST → クライアントがリクエストを送る
@Post()メソッドに一致 → NestJSが controller.ts の create() を呼ぶ
@Body()でDTOに変換 → JSONが CreateDiaryEntryDto にマッピングされる
④ serviceに渡す → create(dto)メソッドでデータ処理(保存)
⑤ 結果を返す → 生成されたオブジェクトが controller → curl に返る

STEP7:TypeORM + SQLiteでDBにデータを永久保存

TypeORM

・TSで使えるObject Relational Mapper、TSクラスを使用したDBテーブルとデータをやり取りするライブラリ
→TypeORMを使用すると、SQLがなくても操作可能。
→ディスク上に永久保存(DB保存)。↔︎Node.jsのRAMに保存。
→ORM=オブジェクト(データと処理を一つの単位、再利用可能)とRDB(SQL)を繋ぐライブラリ
→オブジェクト指向で表現することで、スパゲティ化(ぐちゃる、メンテしづらい)を防止
→オブジェクト指向にすることで、constructorで管理が可能。

NestJS↔︎TypeORM(Entityクラス)↔︎SQLite

①依存ライブラリのインストール
pnpm add @nestjs/typeorm typeorm sqlite3

・@nestjs/typeormで、NestJSのDI(Dependency Injection:依存を外部注入、newを描かなくていい)
・モジュール構造(@Module下のimports, controllers, providersなど。ちなみにAngular由来)に沿ったDB接続
・管理が可能になるNest公式ラッパー(専用モジュール)、NestJS視点では生のTypeORMは使いづらいため。

→typeormは、TS対応ORM(TypeORM)本体、EntityクラスでDB操作可能(CRUD操作)
→sqlite3で、TypeORMがSQLiteのDBを読み書き可能に。
→NestJSはDIをTSで実現したモダンフレームワーク。

②TypeORM設定ファイルの編集
// src/app.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DiaryEntry } from './diary-entry/entities/diary-entry.entity';
import { DiaryEntryModule } from './diary-entry/diary-entry.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'db.sqlite',
      entities: [DiaryEntry],
      synchronize: true, // 開発中のみtrue(本番はfalse推奨)
    }),
    DiaryEntryModule,
  ],
})
export class AppModule {}
③Entityファイルの編集(DBのテーブルを定義)
// src/diary-entry/entities/diary-entry.entity.ts

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class DiaryEntry {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  key: string;

  @Column()
  value: string;

  @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
  createdAt: Date;
}
④ModuleにRepository登録
// src/diary-entry/diary-entry.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DiaryEntry } from './entities/diary-entry.entity';
import { DiaryEntryService } from './diary-entry.service';
import { DiaryEntryController } from './diary-entry.controller';

@Module({
  imports: [TypeOrmModule.forFeature([DiaryEntry])],
  controllers: [DiaryEntryController],
  providers: [DiaryEntryService],
})
export class DiaryEntryModule {}
⑤Serviceファイルの編集(DB保存へ切り替え)
// src/diary-entry/diary-entry.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DiaryEntry } from './entities/diary-entry.entity';
import { CreateDiaryEntryDto } from './dto/create-diary-entry.dto';

@Injectable()
export class DiaryEntryService {
  constructor(
    @InjectRepository(DiaryEntry)
    private readonly diaryRepo: Repository<DiaryEntry>,
  ) {}

  create(dto: CreateDiaryEntryDto) {
    const entry = this.diaryRepo.create({
      key: dto.key,
      value: dto.value,
    });
    return this.diaryRepo.save(entry);
  }

  findAll() {
    return this.diaryRepo.find({ order: { createdAt: 'DESC' } });
  }
}
⑥動作確認

・POST

curl -X POST http://localhost:3000/diary-entry \
  -H "Content-Type: application/json" \
  -d '{"key": "sleepTime", "value": "23:45"}'

・GET

curl http://localhost:3000/diary-entry

値が取れたらOK!
バックエンドでの開発は一旦終了です。

お疲れ様でした。
次は、フロントエンドの開発に入ります。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?