LoginSignup
3
2

More than 3 years have passed since last update.

express+TypeScriptでAPIを構築する際にやったこと

Posted at

はじめに

今まではPHPでAPIを構築することが多かったが、メンバーのスキルセットがjs寄りだったため、
express(node.js)でAPIを構築することになったので、その際のメモ。

環境

  • Windows10
  • Docker for Windows:3.0.0
  • node.js:v14.15.1
  • npm:6.14.8
  • TypeScript:4.1.3
  • express:4.16.1
  • MySQL:8.0.21
  • eslint:7.15.0
  • prettier:2.2.1

Dockerで動作環境を構築

docker-compose.ymlはこんな感じ

docker-compose.yml
version: "3"
services:
  mysql:
    image: mysql:8
    command: --default-authentication-plugin=mysql_native_password
    env_file: ./mysql/.env
    ports:
      - 3306:3306
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/conf/my.cnf:/etc/my.cnf
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    env_file: ./phpmyadmin/.env
    ports:
      - 8080:80
  app:
    image: node:12
    env_file: ../api-dir/.env # Docker用のディレクトリと同列にAPIのディレクトリを配置
    tty: true
    ports:
      - 3000:3000
    volumes:
      - ../api-dir:/app
    working_dir: /app
    command: npm start
volumes:
  mysqldata:

expressの用意

まずDocker用のディレクトリと同列にAPIのディレクトリを作成する

$ cd ../
$ mkdir api-dir
$ cd api-dir

必要なモジュールをnpmでインストール(主なもの)

  • express:node.jsのフレームワーク
  • express-validator:バリデーション
  • knex:クエリビルダー、マイグレーション、シーディング等
  • winston:ログ
  • jest:UnitTest
  • supertest:jestでのAPIリクエストテスト用
  • json2csv:csv変換
  • nodemon:コード変換時の自動再起動
  • ts-node:TypeScriptのトランスパイル
  • eslint:静的解析
  • prettier:コード整形

node.jsのクエリビルダーで良いものがないか探し回った末、knexにたどり着いた。
Laravelに慣れていたが、違和感なく使えた。
公式ドキュメントも充実しているのでオススメ。

各種設定ファイルの作成

package.json(一部抜粋)
"scripts": {
    "start": "NODE_ENV=development npx nodemon --exects-node", // 起動コマンドを変更
    "lint:fix": "eslint */**/*.ts *.ts *.json --fix",
    "db:down": "knex --knexfile knexfile.ts migrate:rollback --all --env test",
    "db:migrate": "knex --knexfile knexfile.ts migrate:latest --env test",
    "db:seed": "knex --knexfile knexfile.ts seed:run --env test",
    "db": "npm run db:down && npm run db:migrate && npm run db:seed",
    "test": "jest --config jestconfig.json --coverage --verbose --runInBand --detectOpenHandles",
}
nodemon.json
{
  "watch": ["src", "seeds", "migrations", "tests"],
  "ext": "ts",
  "exec": "ts-node ./bin/www"
}

jestconfig.json
{
  "testMatch": ["**/?(*.)+(spec|test).+(ts|tsx|js)"],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
  "setupFilesAfterEnv": ["./jest.setup.js"] // タイムアウトの秒数を調整
}
jest.setup.js
jest.setTimeout(5000)
knexfile.ts
if (!require.cache['dotenv']) {
  require('dotenv').config()
}

module.exports = {
  development: { // 開発用
    client: 'mysql',
    connection: {
      host: process.env.MYSQL_SERVER,
      user: process.env.MYSQL_USER,
      password: process.env.MYSQL_PASSWORD,
      database: process.env.MYSQL_DATABASE,
      port: process.env.MYSQL_PORT,
    },
    migrations: {
      extension: 'ts',
      tableName: 'knex_migrations',
    },
  },

  test: { // Jest用
    client: 'mysql',
    connection: {
      host: '127.0.0.1',
      user: process.env.MYSQL_USER,
      password: process.env.MYSQL_PASSWORD,
      database: process.env.MYSQL_DATABASE,
      port: process.env.MYSQL_PORT,
    },
    migrations: {
      extension: 'ts',
      tableName: 'knex_migrations',
    },
  },
}
winston.ts(おまけ)
import fs from 'fs'
import path from 'path'
import moment from 'moment'
const winston = require('winston')
require('winston-daily-rotate-file')

const logDirectory = path.join(__dirname, './logs')
//指定したディレクトリが存在するか?
fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory)

const transport = new winston.transports.DailyRotateFile({
  filename: path.join(logDirectory, './error-%DATE%.log'),
  datePattern: 'YYYY-MM-DD',
  zippedArchive: true,
})

transport.on('rotate', function (oldFilename, newFilename) {
  // do something fun
})

// creates a new Winston Logger
const logger = new winston.createLogger({
  level: 'info',
  transports: [transport],
  exitOnError: false,
})

logger.errorLog = (req, res, err) => {
  logger.error(
    `[${moment().format('YYYY-MM-DD HH:mm:ss')}] ${req.method} - ${
      res.statusCode
    } - ${req.originalUrl} - ${req.ip} - ${err.message}`,
  )
}
module.exports = logger
eslintrc.json
{
  "parser": "@typescript-eslint/parser",
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "prettier/@typescript-eslint",
    "plugin:prettier/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 2020,
    "sourceType": "module"
  },
  "rules": {
    "@typescript-eslint/no-var-requires": "off",
    "@typescript-eslint/no-unused-vars": "off",
    "prettier/prettier": [
      "error",
      {
        "trailingComma": "all",
        "endOfLine": "lf",
        "semi": false,
        "singleQuote": true,
        "printWidth": 80,
        "tabWidth": 2
      }
    ]
  }
}

起動

expressの起動

$ cd docker-dir
$ docker-compose up -d // コンテナ起動時にnpm startが実行され、nodemonで変更監視される

テストの際

$ cd docker-dir
$ docker-compose up mysql phpmyadmin -d
$ cd ../api-dir
$ npm run db // DBのセットアップ
$ npm run test

終わりに

今回、フロントエンドはVue.js(TypeScript)、APIはexpress(TypeScript)を採用した結果、
フロントエンジニアにもAPI製造を手伝ってもらうことができた。
言語の壁はあまり感じることなく、スムーズに作業を進められている様子だった。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2