Typescript + express + Dockerで、APIサーバーを構築する
記事の内容
Expressで新アプリ用APIを開発するため、Dockerでローカル開発環境を構築する
- Express
- Node.js
- Docker
- Typescript
を使用する。
対象読者
- Express(node.js)で開発したい人
- ExpressのprojectにTypescriptを導入したい人
- Dockerでローカル開発環境を作りたい人
環境
- node:16.13.1
- express 4.16.1
- mac OS
Express環境を構築する
projectを作成する
yarn init & add express
yarn initを行い、package.jsonを作成します。
$ yarn init
yarn init v1.22.17
question name (node-test): node-test
question version (1.0.0):
question description:
question entry point (index.js): server.js
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
✨ Done in 22.45s.
entry pointはserver.js
にしましたが、お好みで大丈夫です。
次にexpressをinstallします
$ yarn add express
expressでwebサーバーを構築する
次に、server.js
とapp.js
を作成します。
$ mkdir src
$ touch src/server.js
$ touch src/app.js
※src
ディレクトリにソースファイルを配置していきます。
server.js
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('./app');
var debug = require('debug')('test:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
app.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('hello!');
})
module.exports = app;
現在は以下のようなディレクトリ配置になっています。
$ tree -I node_modules
.
├── package.json
├── src
│ ├── app.js
│ └── server.js
└── yarn.lock
express serverを起動する
$ node src/server.js
localhost:3000 にて、hello!
という文字が表示されたらexpress serverが起動しています。
yarnコマンドを設定する
yarnコマンドでserverを起動できるように、package.jsonを修正する
package.json
{
"name": "node-test",
"version": "1.0.0",
"main": "server.js",
"license": "MIT",
"scripts": {
"start": "node ./src/server.js"
},
"dependencies": {
"express": "^4.17.2"
}
}
以下コマンドで同様にexpressが起動するようになる
$ yarn start
Dockerでnode環境を作成する
expressが立ち上がったので、Dockerで開発できるようにDocker環境を構築していきます。
Dockerfileの作成
$ touch Dockerfile
Dockerfile
FROM node:16.13.1-alpine
WORKDIR /usr/src/app
COPY ./package* ./
COPY ./yarn* ./
RUN yarn
COPY . .
EXPOSE 3000
CMD yarn start
docker buildを行い、正常に動作することを確認する
$ docker build . -t express-test
$ docker run --name express-test-container -p 3000:3000 -d express-test
上記コマンドを実行後、同様にlocalhost:3000にhello!
の文字が表示されていたら、Dockerでexpressが実行されていることが確認できる。
docker-compose.ymlの作成
ローカル環境でMySQLなどと連携できるように、docker-compose.yml
を作成する
$ touch docker-compose.yml
docker-compose.yml
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: nodetest_app
volumes:
- /usr/src/node_modules
- .:/usr/src/app
command: yarn start
ports:
- "3000:3000"
以下コマンドで確認
$ docker-compose up
同様にブラウザで確認ができたらdocker-composeで開発環境を起動できるようになりました。
.dockerignoreファイルの作成
dockerへコピーしなくても良いファイルを.dockerignoreに記述しておく
$ touch .dockerignore
.dockerignore
node_modules
npm-debug.log
.DS_Store
以上で、docker + expressの開発環境が完成した。
開発効率をあげるために
現在のアプリケーションの場合、ファイルに変更があっても、webサーバーが再起動しないため、変更が反映されない。
そのため、ファイルに変更が発生したら自動で再起動するように、nodemon
というpackageを利用する
$ yarn add -D nodemon
package.json
を変更して、nodemonを使ってファイルを起動するようにする
package.json
{
"name": "chimes-api",
"version": "1.0.0",
"main": "server.js",
"license": "MIT",
"scripts": {
"start": "nodemon ./src/server.js"
},
"dependencies": {
"express": "^4.17.2"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
修正が完了したら、再度、docker-compose up
でDocker環境を立ち上げて、ファイルを変更する。
自動でファイルへの変更が反映されたら完了。
typescriptを導入する
- Docker
- express
- nodemon
の開発環境が作成できたので、Typescriptの導入へ進む
typescriptと必要packageをinstall
$ yarn add -D typescript @types/express @types/cookie-parser @types/morgan @types/http-errors
typscriptを初期化
$ npx tsc --init
tsconfig.json
というファイルが作成される
出力先ディレクトリを作成し、出力先を変更する
$ mkdir dist
※src配下はtypescriptを配置し、dist配下にjsファイルが出力されるようにする
tsconfig.json
"outDir": "./dist/",
既存ファイルをtsに変更する
- app.js
- server.js
を
- app.ts
- server.ts
に変更し、typescriptの記述に変更する
app.ts
const express = require('express');
const app = express();
import { Request, Response } from 'express';
app.get('/', (req: Request, res: Response) => {
res.send('hella!');
})
module.exports = app;
server.ts
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('./app');
var debug = require('debug')('src:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val: string): number | string | boolean {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error: any): void {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening(): void {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
compileして実行してみる
$ npx tsc
dist配下に、app.jsとserver.jsが出来る
$ node dist/server.js
compile後のファイルを使用してexpressサーバーが起動する
自動でcompileとサーバー再起動を行う
package.json
を修正して、nodemonのサーバー再起動とtypescriptのコンパイルを自動で行うように設定する
package.json
{
"name": "chimes-api",
"version": "1.0.0",
"main": "server.js",
"license": "MIT",
"scripts": {
"start": "nodemon ./dist/server.js",
"build": "npx tsc",
"build:watch": "npx tsc -w",
"dev": "yarn run build:watch & yarn start"
},
"dependencies": {
"express": "^4.17.2"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.13",
"@types/http-errors": "^1.8.2",
"@types/morgan": "^1.9.3",
"nodemon": "^2.0.15",
"typescript": "^4.5.5"
}
}
以上に設定して、コマンドを実行する
$ yarn dev
これで、compile後のファイルをnodemonで監視するようになったため、開発効率が上がります。
docker-compose.ymlの修正
最後に、docker-compose.yml
のcommandを修正します。
docker-compose.yml
version: '3'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: nodetest_app
volumes:
- /usr/src/node_modules
- .:/usr/src/app
command: yarn dev
ports:
- "3000:3000"
最終確認
ディレクトリ構成
$ tree -I node_modules
.
├── Dockerfile
├── dist
│ ├── app.js
│ └── server.js
├── docker-compose.yml
├── package.json
├── src
│ ├── app.ts
│ └── server.ts
├── tsconfig.json
└── yarn.lock
起動コマンド
$ docker-compose up
これで正常にexpress環境が立ち上がれば、完了です!
note
勉強法やキャリア構築法など、エンジニアに役立つ記事をnoteで配信しています。
Discussion