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)
- Controllerから entryDto(key/valueのデータ)を受け取る
- 自動的に id をインクリメント(1, 2, 3…)
- createdAt に現在の日時を付与
- それを entries[] 配列に保存
- 登録されたデータを返す(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!
バックエンドでの開発は一旦終了です。
お疲れ様でした。
次は、フロントエンドの開発に入ります。