内容
今回はNode.jsのORMであるSequelizeを使って、ExpressサーバーからMySQLにアクセスしデータを取得・レスポンスするまでを行っていきます。
前回の記事まででExpressサーバーと簡単なAPIの作成が完了しているので、今回はSequelize導入とModel作成を中心に行っていこうと思います。
前回までの内容
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Vue編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~MySQL編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Express編~
作業手順
- コンテナ同士のネットワークを設定
- Sequelize 導入
- Model作成
- GetTests APIを完成させる
- フロント側からAxiosでAPIをコールする
- レスポンスをVuexにバインドして描写する
1. コンテナ同士のネットワークを設定
docker-compose.yml
version: "3"
services:
app:
container_name: app_container
build: ./docker/app
ports:
- 8080:8080
volumes:
- ./app:/app
stdin_open: true
tty: true
environment:
TZ: Asia/Tokyo
command: yarn serve
networks: #ネットワークを追加
- default
api:
container_name: api_container
build: ./docker/api
ports:
- 3000:3000
volumes:
- ./api:/api
tty: true
environment:
CHOKIDAR_USEPOLLING: 1
TZ: Asia/Tokyo
depends_on:
- db
command: yarn nodemon
networks: #ネットワークを追加
- default
db:
container_name: db_container
build: ./docker/db
image: mysql:5.7
ports:
- 3306:3306
volumes:
- ./db/conf/my.cnf:/etc/mysql/conf.d/mysql.cnf
- ./db/init_db:/docker-entrypoint-initdb.d
- test_data:/var/lib/mysql
environment:
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- TZ="Asia/Tokyo"
networks: #ネットワークを追加
- default
networks: #ネットワークを追加
default:
volumes:
test_data:
共通のネットワークで接続することでそれぞれのコンポーネント間でやりとりが可能になります。
コンテナ起動
$ docker-compose up -d
##2. Sequelize 導入
ここからはNode.js用のORM(リレーショナルデータベースからデータを取得・操作するもの)であるSequelizeを導入し、DBからデータを取得していきます。
コンテナにアクセス
$ docker exec -it api_container sh
必要なライブラリをインストール
- sequelize
- Node.js用のORM
- sequelize-typescript
- TypeScriptベースで記述できるよう導入
- mysql2
- Sequelize利用のために必要
- reflect-metadata
- デコレーター記述するために導入
- log4js
- ログ出力するために導入
- cors
- CORS(Cross-Origin Resource Sharing)対応するために導入
- dotenv
- 環境変数の代わりにenvファイルから読み取るために導入
$ yarn add sequelize@5.21.11 sequelize-typescript reflect-metadata mysql2 log4js cors
$ yarn add --dev dotenv @types/validator @types/bluebird @types/cors @types/dotenv
※sequelize のバージョンによってはエラーになるので、インストール時にバージョン確認が必要!
api/index.ts
import cors from 'cors' // 追加
import express from 'express'
import router from './routes/index'
const app = express()
const port = 3000
app.use(cors()) // 追加
app.use('/api', router)
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
##3. Model 作成
api/models/settings/.env
MYSQL_DATABASE=test_db
MYSQL_USER={MySQLコンテナ作成時に設定したもの}
MYSQL_PASSWORD={MySQLコンテナ作成時に設定したもの}
MYSQL_ROOT_PASSWORD={MySQLコンテナ作成時に設定したもの}
ここはルートディレクトリにある.envと同じ設定。
api/models/settings/db_setting.ts
import dotenv from 'dotenv'
dotenv.config({ path: __dirname + '/.env' })
interface DatabaseTypes {
database: string | undefined
user: string | undefined
password: string | undefined
}
export const dbSetting: DatabaseTypes = {
database: process.env.MYSQL_DATABASE,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
}
Sequelize用のパスワードやテーブルなどの設定をしておきます。
api/models/test.ts
import {
Table,
Model,
Column,
DataType,
PrimaryKey,
AutoIncrement,
} from 'sequelize-typescript'
@Table({
modelName: 'test',
tableName: 'test',
})
export class Test extends Model<Test> {
@PrimaryKey
@AutoIncrement
@Column(DataType.INTEGER)
readonly id!: number
@Column(DataType.STRING)
name!: string
@Column(DataType.STRING)
description!: string
}
test テーブル用のモデルを作成。デコレーターベースで記述。
TypeScriptのエラーが出るのでルールを追加。
api/tsconfig.json
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["./**/*"]
}
tsconfigの設定を上記に編集
api/models/index.ts
import log4js from 'log4js'
import { Sequelize } from 'sequelize-typescript'
import { dbSetting } from './settings/db_setting'
import { Test } from './test'
const logger = log4js.getLogger('mysql')
export default new Sequelize({
dialect: 'mysql',
timezone: '+09:00',
port: 3306,
host: 'db',
username: dbSetting['user'],
password: dbSetting['password'],
database: dbSetting['database'],
logging: (sql: string) => {
logger.info(sql)
},
define: { timestamps: false, underscored: true },
pool: { max: 5, min: 0, idle: 10000, acquire: 30000 },
models: [Test],
})
export { Test }
ここが今回の肝。
エラーが出まくるとするとここなはずです。
ライブラリのバージョンが異なったり、tsconfigの設定がうまくいっていないとここでエラーがおきます。
##4. GetTests APIを完成させる
api/routes/tests/get_tests.ts
import { Request, Response } from 'express'
import { Handler } from '../../core/handler'
import { NO_DATA_EXISTS } from '../../constants/error'
import { Test } from '../../models/index'
export class GetTests {
handler: Handler
constructor(req: Request, res: Response) {
this.handler = new Handler(req, res)
}
/**
* メイン処理
*/
async main() {
const data = await this.getTests()
if (!data) {
return this.handler.error(NO_DATA_EXISTS)
}
return this.handler.json(data)
}
/**
* Testデータを全て取得
*/
getTests() {
return Test.findAll({
attributes: ['id', 'name', 'description'],
})
}
}
処理フローとしては
- main 関数がインスタンス化されて呼ばれる
- main 関数内で getTests 関数が呼ばる
- getTests 内で Testモデルに対し findAll 関数(データを全て取得)を走らせることでtestテーブルからデータを取得
- getTests 関数のリターン値が存在しなければエラーを返し、存在すればjson形式でデータを返す
確認!
localhost:3000/api/tests にアクセス!
実際のデータがJSONで返ってくればOK!
指定したIDから1件取得するAPIを作成
api/routes/tests/get_test_by_id.ts
import { Request, Response } from 'express'
import { Handler } from '../../core/handler'
import { PARAMETER_INVALID, NO_DATA_EXISTS } from '../../constants/error'
import { Test } from '../../models/index'
type Params = {
test_id: number
}
export class GetTestById {
handler: Handler
params: Params
constructor(req: Request, res: Response) {
this.handler = new Handler(req, res)
this.params = {
test_id: Number(req.params.test_id),
}
}
/**
* メイン処理
*/
async main() {
if (!this.params.test_id) {
return this.handler.error(PARAMETER_INVALID)
}
const data = await this.getTest()
if (!data) {
return this.handler.error(NO_DATA_EXISTS)
}
return this.handler.json(data)
}
/**
* Testデータを全て取得
*/
getTest() {
return Test.findOne({
attributes: ['id', 'name', 'description'],
where: {
id: this.params.test_id,
},
})
}
}
今回は findOne関数(該当データを一件取得)内で where句による検索をかけてIDが該当したデータを取得します。
testsController.ts
import { Router } from 'express'
import { GetTests } from '../tests/get_tests'
import { GetTestById } from '../tests/get_test_by_id' // 追加
const router = Router()
router.get('/', (req, res, next) => {
new GetTests(req, res).main().catch(next)
})
router.get('/:test_id', (req, res, next) => { // 追加
new GetTestById(req, res).main().catch(next) // 追加
}) // 追加
export default router
検索する対象のIDはURLのQueryとして GetTestById クラス に渡します。
確認!
localhost:3000/api/tests/1 にアクセス
/tests/{ここで指定したID} のデータが返ってくれば成功!
試しに他のIDでも確認してください。
##5. フロント側からAxiosでAPIをコールする
ここまででサーバー側は完了です。
ここからは作成したAPIを実際にフロント側から呼ぶ設定を行っていきます。
コンテナから抜ける
$ exit
appコンテナにアクセス
$ docker exec -it app_container sh
Axios導入
$ yarn add axios
app/src/utils/axios.ts
import axios from 'axios'
export const api = axios.create({
baseURL: 'http://localhost:3000/api',
})
cors対策とデフォルトURL化を兼ねてモジュール定義をしておきます。
呼び出す際は api.get や api.post でコールすることが可能になります。
app/src/store/modules/test.ts
import {
getModule,
Module,
VuexModule,
Mutation,
Action,
} from 'vuex-module-decorators'
import store from '../index'
import { api } from '../../utils/axios'
type TestType = {
id: number
name: string
description: string
}
type TestState = {
apiTests: TestType[]
}
@Module({ store, dynamic: true, namespaced: true, name: 'Test' })
class TestModule extends VuexModule implements TestState {
apiTests: TestType[] = []
@Mutation
SET_API_TESTS(payload: TestType[]) {
this.apiTests = payload
}
@Action
async getTests() {
const response = await api.get('/tests')
if (response.data.data) {
this.SET_API_TESTS(response.data.data)
}
}
}
export const testModule = getModule(TestModule)
Vuexの test Store を作成し、Actionの getTests 内で作成したAPIをコールすることで取得したデータを apiTests ステートに格納します。
app/src/pages/Test.vue
<template>
<div class="test">
<v-data-table :headers="headers" :items="tests"> </v-data-table>
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import { testModule } from '../store/modules/test'
@Component
export default class Test extends Vue {
tests = []
headers = [
{ text: 'ID', align: 'center', value: 'id' },
{ text: '名前', align: 'center', value: 'name' },
{ text: '詳細', align: 'center', value: 'description' },
]
async created() {
await testModule.getTests()
this.tests = testModule.apiTests
}
}
</script>
Vueがcreateされた際に testストアの getTests アクションを走らせることでデータが取得され、apiTestsのデータをバインドすることでVue内でデータを参照します。
そのデータをVuetifyの v-table-table に渡してあげれば完了。
確認!
localhost:8080
ここまでフロント側でAPIをコールし、DBからデータを取得し、返ってきたデータをVuexに格納し、それを描写することができました。
ここまででベースができたので同じ要領でAPIとページを追加していけば拡張が可能となります。
ここまででテンプレートが完成なので、次回は簡単なCLUD操作ができるアプリを作成していこうと思います!
次回
※ 別途作成中
参考
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Vue編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~MySQL編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Express編~