【連載】 データベースを使ってみよう
💡 この記事は GMOペパボ インターンシップ2020 の連載記事です。
こんにちは!引き続き dojineko がお送りします。予定しているカリキュラムの三番目の内容となります。
■ カリキュラム
・マネクラの構成紹介
・初めてのWebAPIを作ってみよう
・データベースを使ってみよう ← イマココ
・ログイン機能を作ってみよう
・ミニブログを作ってみよう
・ミニブログに機能を追加してみよう
🍟 今回作成するアプリについて
今回は、前回作った WebAPI のアプリにデータベースに対してデータの読み書きを行う処理を追加してメモをデータベースに記録できるような仕組みを作ってみます。目指す完成形としては以下のように、リクエストを行った場合に対応するレスポンスを返すものを想定しています。
# データの追加
curl -X POST -H 'Content-Type: application/json' -d '{"title":"sample", "body":"hello, world!"}' http://localhost:3000/api/notes
# データの読み取り
curl -X GET -H 'Content-Type: application/json' http://localhost:3000/api/notes/****
# データの削除
curl -X DELETE -H 'Content-Type: application/json' http://localhost:3000/api/notes/****
🧰 環境構築とパッケージの追加
今回データベースはマネクラのプロジェクトに付属するものと同じ MySQLデータベースを利用します。Node.js で MySQL を利用するためのパッケージは数多くありますが、今回は TypeORM を使用します。
前回と同じ作業ディレクトリで、以下のコマンドを実行し必要になるパッケージを追加します。
npm install typeorm reflect-metadata mysql body-parser @types/body-parser uuid @types/uuid
加えて手元にMySQLの環境を作成します。Homebrewからインストールする場合を例示します。
# インストール
brew install mysql
# 起動
brew services start mysql
起動できたら MySQL CLI を起動します。
mysql -u root
続いて下記のSQLを発行し、アプリケーション専用のデータベースとMySQLユーザーを作成します。パスワードは適宜変更しましょう。
CREATE DATABASE myapp;
CREATE USER myapp@localhost;
GRANT ALL ON myapp.* TO myapp@localhost WITH GRANT OPTION;
FLUSH PRIVILEGES;
完了したら Ctrl + C で MySQL CLI を終了します。
ここまでの作業で、手元の環境でアプリケーションからデータベースを使用する準備ができました。
🔰 手元の環境で作ってみよう
準備ができたので早速データベースへの書き込みを行ってみましょう。
まずはデータ構造を決定するクラスを定義します。「entities/note.ts」となるよう下記の内容を保存してください。専用のデータベース上に notes というテーブルを用意しメモのタイトル、本分、作成日時を記録できるようにしています。このクラスの構造は後に設定するTypeORMの設定で自動でデータベースに反映されるようになります。
import { Entity, Column, PrimaryColumn, CreateDateColumn } from 'typeorm'
@Entity({ name: 'notes' })
export class NoteEntity {
@PrimaryColumn({ type: 'uuid' })
public id!: string
@Column({ type: 'varchar', length: 255 })
public title!: string
@Column({ type: 'text' })
public body!: string
@CreateDateColumn({ name:'created_at', type: 'timestamp' })
public createdAt!: Date
}
続いてアプリケーションがデータベースに接続できるように設定を行います。index.ts を開いて下記のように変更してください。
import 'reflect-metadata'
import express, { Request, Response, NextFunction } from 'express'
import bodyParser from 'body-parser'
import { v4 as uuid } from 'uuid'
import { createConnection, getManager } from 'typeorm'
import { NoteEntity } from './entities/note'
(async () => {
type routeHandler = (req: Request, res: Response, next: NextFunction) => Promise<void>
const wrap = (fn: routeHandler): routeHandler => (req, res, next) => fn(req, res, next).catch(next)
const app = express()
const port = process.env.NODE_ENV === 'production' ? 80 : 3000
await createConnection({
synchronize: true,
type: 'mysql' as const,
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : 3306,
username: process.env.DB_USER || 'myapp',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'myapp',
entities: [
NoteEntity,
],
})
app.use(bodyParser.json())
app.get('/api/hello', (req, res) => {
res.json({ hello: 'world!' })
})
// Part1: メモを追加する処理を実装します
// Part2: メモを読み込む処理を実装します
// Part3: メモを削除する処理を実装します
app.listen(port, () => console.log(`ready http://localhost:${port}`))
})()
ポイントを抜粋すると、まず前回までのコードに createConnection を追加しました。これにはTypeORMがMySQLに接続するための設定を記述しています。process.env.*** という記載が何度か出てきていますが、ここで指定した環境変数が設定されている場合は接続情報を環境変数から取得するようにしています。この後にマネクラにデプロイするときに利用する仕組みです。
また、設定に synchronize: true を設定しました。先程定義した NoteEntity に対応するようにデータベースを自動で更新する設定です。ここまででアプリケーションがデータベースに接続できるようになりました。
続いてWebAPIを介してデータベースに読み書きできるように機能を追加しましょう。index.ts の「// Part1: メモを追加する処理を実装します」の部分に以下のコードを追加します。
// Part1: メモを追加する処理を実装します
app.post('/api/notes', wrap(async (req, res) => {
const title: string = req.body.title || ''
const body: string = req.body.body || ''
if (!title || !body) {
res.sendStatus(400)
return
}
const mgr = getManager()
const result = await mgr.save(NoteEntity, {
id: uuid(),
title,
body,
})
res.status(201).json(result)
}))
このコードではリクエスト内容から title と body を取得し、もしどちらかの内容が空の場合は 400 エラーになるようにしています。チェックを通過したら、データベースへ内容を保存する処理を実行し、結果を 201 ステータスでレスポンスするようになっています。
次はデータベースから読み込む処理を実装しましょう。index.ts の「// Part2: メモを読み込む処理を実装します」の部分に以下のコードを追加します。
// Part2: メモを読み込む処理を実装します
app.get('/api/notes/:id', wrap(async (req, res) => {
const id: string = req.params.id || ''
if (!id) {
res.sendStatus(400)
return
}
const mgr = getManager()
const result = await mgr.findOne(NoteEntity, { id })
if (!result) {
res.sendStatus(404)
return
}
res.status(200).json(result)
}))
このコードでは追加したメモの ID をもとにデータベースから検索を行いない場合は404エラーをレスポンスし、あった場合は見つかった内容をレスポンスします。
最後にデータベースから削除する処理を実装しましょう。index.ts の「// Part3: メモを削除する処理を実装します」の部分に以下のコードを追加します。
// Part3: メモを削除する処理を実装します
app.delete('/api/notes/:id', wrap(async (req, res) => {
const id: string = req.params.id || ''
if (!id) {
res.sendStatus(400)
return
}
const mgr = getManager()
const result = await mgr.findOne(NoteEntity, { id })
if (!result) {
res.sendStatus(404)
return
}
await mgr.delete(NoteEntity, { id })
res.sendStatus(204)
}))
今回追加したコードは読み込みのコードと似ていますが、findOne ではなく delete を実行している点と、成功時に 204 ステータスのみレスポンスする点が異なります。
ここまでで今回作るAPIはすべて用意できたので実際に実行してみましょう。ファイルを保存して、「npm run start」でアプリケーションを起動します。アプリケーションが起動したらそれぞれ下記のようなコマンドでAPIを実行しデータベースへの読み書きを試してみましょう。
# データの追加
curl -X POST -H 'Content-Type: application/json' -d '{"title": "hoge", "body": "fuga"}' http://localhost:3000/api/notes
# データの読み込み
curl -X GET http://localhost:3000/api/notes/<id>
# データの削除
curl -X DELETE http://localhost:3000/api/notes/<id>
🪁 マネクラで動かしてみよう
手元で動作を確認できたらマネクラにデプロイして動かしてみましょう。
まずはマネクラでデータベースを利用する設定を行います。プロジェクト詳細画面からデータベースの接続情報を確認しましょう。なお設定したパスワードを忘れてしまった場合はパスワードを変更して再設定を行います。
確認した情報をもとに、環境変数設定を行います。
設定画面でそれぞれ下記のように設定し、登録内容を反映するボタンをクリックして反映します
・DB_NAME: データベース名の値
・DB_USER: ユーザー名の値
・DB_PASSWORD: 設定したデータベースパスワード
・DB_HOST: データベースのホスト名
無事設定ができたら前回同様、手元のコードの変更を git に記録してデプロイします。
# 変更を記録対象にする
git add .
# コミットを作成する
git commit -m "use database"
# リモートに転送する
git push lolipop master
デプロイが成功し、アプリケーションが無事起動したら手元の環境同様データベースの構造がアプリケーションに適合するように自動的に更新されます。あとは手元で行ったのと同じようにAPIを操作してデータベースの読み書きができるか試してみましょう。
# データの追加
curl -X POST -H 'Content-Type: application/json' -d '{"title": "hoge", "body": "fuga"}' https://*****.lolipop.io/api/notes
# データの読み込み
curl -X GET https://*****.lolipop.io/api/notes/<id>
# データの削除
curl -X DELETE https://*****.lolipop.io/api/notes/<id>
📚 まとめ
今回は WebAPI を介してMySQLデータベースに対してデータの追加、取得、削除を行いました。プロジェクトに付属するデータベースを活用してアプリケーションからデータを保存して利用する方法のイメージができたのではないかと思います。
なお、今回はサンプルということで、APIに対してなんの認証も行っていないため、誰でも内容を記録したり取得できる状態です。次回は、APIに対して認証処理を追加してアプリケーションを安全にホストするための方法に取り組んでいきたいと思います。