LoginSignup
1

More than 1 year has 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製造を手伝ってもらうことができた。
言語の壁はあまり感じることなく、スムーズに作業を進められている様子だった。

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
What you can do with signing up
1