内容
Docker上でExpress APIサーバーコンテナを構築しシンプルなレスポンスを返すまでの環境を構築していきます。
前回の記事まででアプリケーションコンテナ / DBコンテナを作成しましたが、今回はそれらを繋ぎ合わす役目のAPIサーバーコンテナを構築し実際にデータをフロントに渡すまでを行っていきます。
サーバー側はボリュームが多いため、Expressの基本構築とSequelizeの導入の記事を分けております。
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Vue編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~MySQL編~
作業手順
- APIコンテナ作成
- ts-node/nodemonの導入
- Expressサーバー構築
- ルーティング設定
- APIハンドラーの設定
- APIのテンプレートを作成
##1. APIコンテナ作成
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
# ここから追加
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
# ここまで追加
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"
volumes:
test_data:
docker/api/Dockerfile
FROM node:12.13-alpine
ENV NODE_PATH /usr/local/lib/node_modules
WORKDIR /api
起動確認
$ docker-compose up -d
$ docker ps
api_container が起動してればOK!
apiコンテナアクセス
$ docker exec -it api_container sh
2. ts-node/nodemonの導入
必要なライブラリを導入
$ yarn init
$ yarn add --dev typescript ts-node prettier @types/node
.prettierrc
{
"singleQuote": true,
"semi": false
}
tsconfig init
$ ./node_modules/.bin/tsc --init
tsconfig.jsonが生成されるのでこちらをベースにアプリを構築していきます。
tsconfig.json
"include": ["./**/*"] //末尾に追加
package.json
"scripts": {
"ts-node": "ts-node index.ts"
},
ts-node でTypeScriptファイルを実行できるようにスクリプトを設定しておきます。
api/index.ts
const message: string = 'テスト'
console.log(message)
TypeScriptを実行するためのメインファイルを作成します。
ts-nodeが正常に動作するかチェックするためのダミーを入力します。
テスト
$ yarn ts-node
TypeScriptがコンパイルされ、上記のメッセージが表示されればOKです!
nodemon 導入
ファイルに変更があった際に自動ビルドを行ってくれるライブラリを導入します。
$ yarn add --dev nodemon
nodemon.json
{
"watch": ["api/**/*"],
"ext": "ts",
"exec": "ts-node ./index.ts"
}
nodemonの設定ファイルを作成します。
ts-nodeをnodemon経由で実行し、apiディレクトリ直下を全て監視対象に設定します。
package.json
"scripts": {
"tsc": "tsc",
"ts-node": "ts-node index.ts",
"nodemon": "nodemon"
},
docker-compose.yml
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 #こいつを追加
コンテナ起動時にnodemon経由で自動で立ち上がるようコマンドを追加しておきます。
##3. Expressサーバー構築
ここまででTypeScriptの実行と変更時の自動ビルドの設定が完了しました。
ここから実際のAPIサーバーとなるExpressを構築していきます。
$ yarn add express
$ yarn add --dev @types/express
api/index.ts
import express from 'express'
const app = express()
const port = 3000
app.get('/', (req, res) => res.send('Test Express!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
起動確認
$ exit //コンテナから抜ける
$ docker-compose stop // コンテナを停止
$ docker-compose up -d //コンテナ再起動
localhost:3000 にアクセスして、「Test Express!」と表示されていれば問題なくレスポンスが返っている状態!
念のためindex.tsの「Test Express!」を別のメッセージを変更し、再度 localhost:3000 にアクセスした時に、メッセージが変わるか確認しましょう。自動で変わっていれば nodemon が適切に動き自動ビルドがされている状態です!
##4. ルーティング設定
今はまだ localhost:3000 でしかアクセスできない状態ですが、下記のようにURLに応じたAPIをコールしたいのでルーティングの設定を行っていきます。
例)
- localhost:3000/api/tests
- testテーブルのデータを全件取得
- localhost:3000/api/tests/1
- testテーブルからIDが1のデータを取得
- localhost:3000/api/users
- userテーブルからデータを全件取得
api/routes/index.ts
import Express from 'express'
const router = Express.Router()
export default router
api/routes/tests/testsController.ts
import { Router } from 'express'
const router = Router()
export default router
下処理はここまでで、実際にルーティングを指定していきます。
今回は「localhost:3000/api/tests」でアクセスできるように変更
api/routes/index.ts
import Express from 'express'
const router = Express.Router()
router.get('/', (req, res, next) => {
res.send('テスト')
})
export default router
api/index.ts
import express from 'express'
import router from './routes/index'
const app = express()
const port = 3000
app.use('/api', router)
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
上記で設定した[app.use('/api', router)]こちらの設定で localhost:3000/api が渡ってきた時に routes/index.ts のルーティングへと移動する流れになります。
実際に動作チェックしてみましょう。
localhost:3000/api でアクセス
テストが表示されればOK!
実際のアプリでは複数のDBへのアクセスの際にエンドポイントを分岐する必要があるので、下記のように枝分かれするようにルーティングを設定します。
localhost:3000/api/{この値に応じて分岐}
api/routes/tests/testsController.ts
import { Router } from 'express'
const router = Router()
router.get('/', (req, res, next) => {
res.send('ルーティング完了!')
})
export default router
api/routes/index.ts
import Express from 'express'
import tests from './tests/testsController'
const router = Express.Router()
router.use('/tests', tests)
export default router
確認!
localhost:3000/api/tests へアクセス
「ルーティング完了!」が表示されればOK!
ここまででルーティングの設定は完了です!
おさらい
- api/index.ts
- localhost:3000/api をキャッチして routes/index.ts にパス
- api/routes/index.ts
- /tests をキャッチして tests/testsController.ts にパス
- api/routes/tests/testsController.ts
- 末尾が無い場合にレスポンスを返す
5. APIハンドラーの設定
ついでに、正常なレスポンスが返るパターンとエラー時のレスポンスを切り替えるハンドラーを作成しておきます。
api/constants/error.ts
export type ErrorCode = {
status: number
type: string
message: string
}
/**
* パラメーターが誤っている場合のエラー
*/
export const PARAMETER_INVALID: ErrorCode = {
status: 400,
type: 'PARAMETER_INVALID',
message: 'The parameter is invalid.',
}
/**
* データが存在しない場合のエラー
*/
export const NO_DATA_EXISTS: ErrorCode = {
status: 400,
type: 'NO_DATA_EXISTS',
message: 'No data exists.',
}
エラーパターンが増えた際はこちらにエラーコードを増やしていきます。
api/core/handler.ts
import { Request, Response } from 'express'
import { ErrorCode } from '../constants/error'
/**
* APIのハンドリングをする機能
*/
export class Handler {
constructor(private req: Request, private res: Response) {}
/**
* データの送信
*/
json<T>(data: T): void {
this.res.json({ data: data })
}
/**
* エラーの送信
*/
error(error: ErrorCode): void {
this.res.status(error.status).send({ error: error })
}
}
APIに異常が発生した際はエラー出力に分岐し、正常な場合はjson形式でリターン
6. APIのテンプレートを作成
localhost:3000/api/test でアクセスした際に tests テーブルからデータを全件取得するAPIのテンプレートを作成しておきます。
api/routes/tests/get_tests.ts
import { Request, Response } from 'express'
import { Handler } from '../../core/handler'
export class GetTests {
handler: Handler
constructor(req: Request, res: Response) {
this.handler = new Handler(req, res)
}
/**
* メイン処理
*/
async main() {
const data = this.getTests()
return this.handler.json(data)
}
/**
* メッセージを返す
*/
getTests() {
const message = 'get_tests実行'
return message
}
}
こちらが実際に呼ばれるAPI
api/routes/tests/testsController.ts
import { Router } from 'express'
import { GetTests } from '../tests/get_tests' // 上記のAPIをインポート
const router = Router()
router.get('/', (req, res, next) => {
new GetTests(req, res).main().catch(next) // 変更
})
export default router
確認!
こんな感じでJSON形式で返ってくればOK!
まとめ
お疲れ様でした!
ここまでで アプリケーションコンテナ、DBコンテナ、APIサーバーコンテナができました。
次回からはそれぞれのコンテナをつなげて実際にDBデータをフロントに渡して描写するまでを行っていこうと思います!
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Vue編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~MySQL編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Express編~
Dockerで【TypeScript+Vue+Express+MySQL】の環境を構築する方法~Sequelize編~