😺

Typescript + express + Dockerで、APIサーバーを構築する

2022/02/10に公開

記事の内容

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.jsapp.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で配信しています。

https://note.com/ring_belle/membership

Discussion