年末休みにnodeでAPIサーバを勉強した復習〜part2 単純なAPI作成

#フォルダのリファクタリングを行った

server　
　dist
　docker
　node_module
　src
　　config
　　controllers
　　models
　　routes

とりあえずこのように変更した。
distにはトランスパイルしたjsが入る
dockerには各Dockerの設定とデータ
node_moduleにはmodule
src以下にTypescriptのソース
configには 各種コンフィグ、Express、mongoose等のライブラリの設定
controllersにはControl部分（細かい機能分けはあとで・・）
modelsにはModel（DBの操作）
routesにはルーティング

#ふわっとTypescript対応
https://kuroeveryday.blogspot.com/2018/03/migrating-from-javascript-to-typescript.html
https://www.ikemo3.com/inverted/typescript/javascript-to-typescript/

こんなページを見ながら徐々に移行した
新幹線に乗って実家に帰る途中で作業をした

##拡張子変更
tsconfigを最も緩く設定したため
拡張子を .tsにしたら　大体うごいた

##import
require構文をimportに変更した
それと同時に exportをTypescriptに合わせた
default exportという概念も覚えた
エラーが大量にでるが　大概は @typesをインストールすればなおった
が、
const JwtStrategy = require('passport-jwt').Strategy;
のようなものがエラーが取れなかったので　後回しにした

```js
const path = require('path');
const express = require('express');
const httpError = require('http-errors');


module.exports = app;
```

のようなコードが

```typescript
import path from 'path';
import express from 'express';
import httpError from 'http-errors';


export default app;
```

のようなイメージである

##any
Typescriptはすべての変数に型を書くべきである
stringやnumberは簡単に型を書くことが出来た
が　interface的なものや 関数等面倒なものはとりあえず anyでごまかした

##トランスパイル
タスクランナーでファイル変更したら自動でトランスパイルするようにしたので
docker-compose up　しておけば勝手にトランスパイルしてくれる
非常に便利だ

#DBアクセスの簡単なAPIを作る
ExpressとMongooseを設定し　簡単なAPIを作る
APIのエントリポイントを /apiをし、その下にRouteをつくる

##express設定

```typescript
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(compress());
app.use(methodOverride());

app.use('/api', routes);
```

ミドルウェアは適当に設定してる。とりあえずこれで /apiから始まるリクエストは reoutesへ転送される

##mongoose設定

```typescript
const mongoUri = config.mongo.host;
mongoose.connect(mongoUri, { keepAlive: 1 });
mongoose.connection.on('error', () => {
    throw new Error(`unable to connect to database: ${mongoUri}`);
});
```

configの設定を渡し mongoに接続する

##model
userテーブルのスキーマを作る

```typescript
import mongoose from 'mongoose';
import Joi from "Joi";


const schema = new mongoose.Schema({
    user_id: {
        type: String,
        required: true,
        unique: true
    },
    user_name: {
        type: String,
        required: true
    },
    memo: {
        type: String,
        required: false
    },
    password: {
        type: String,
        required: true
    },
    life_max: {
        type: Number,
        required: true
    },
    life: {
        type: Number,
        required: true
    },
    level: {
        type: Number,
        required: true
    },
    exp: {
        type: Number,
        required: true
    },
    coin: {
        type: Number,
        required: true
    },
    charged_coin: {
        type: Number,
        required: true
    },
    createdAt: {
        type: Date,
        default: Date.now
    },
    updatedAt: {
        type: Date,
        default: Date.now
    }
});


export const User = mongoose.model("User", schema);
export default User;

export namespace UserSchema {

    export const insertSchema = Joi.object({
        user_id: Joi.string().required(),
        user_name: Joi.string().required(),
        memo: Joi.string(),
        password: Joi.string().required(),
        life_max: Joi.number().required(),
        life: Joi.number().required(),
        level: Joi.number().required(),
        exp: Joi.number().required(),
        coin: Joi.number().required(),
        charged_coin: Joi.number().required(),
    });
    export const updateSchema = Joi.object({
        user_name: Joi.string(),
        memo: Joi.string()
    });
};

```

Joiというライブラリが非常に優秀だった
値のバリデートが簡単に実装できる
メールアドレスや電話番号等のバリデートも可能だ

今回は insertとupdate時もスキーマを定義し、値のチェックを入れた

##router
まず /apiから index.tsにルーティングされるようにした

```typescript:index.ts
import express from "express";
import userRoutes from './user.route';

const router = express.Router();

router.use('/user/:user_id/user', userRoutes);

export default router;
```

その後それぞれの機能にルーティングする
ここでは　/user機能へルーティングする


```typescript:user.ts
import { default as express, Response, Request } from "express";
import asyncHandler from 'express-async-handler';
import * as userCtrl from '../controllers/user.controller';

const router = express.Router( { mergeParams: true });

router.route('/status')
    .get(asyncHandler(status_read))
    .put(asyncHandler(status_insert))
    .post(asyncHandler(status_update))
    .delete(asyncHandler(status_delete));


async function status_read(req: Request, res: Response) {
    let user = await userCtrl.status_read(req);
    res.json(user);
}

// for debug
async function status_insert(req: Request, res: Response) {
    console.log(req.body);
    let user = await userCtrl.status_insert(req);
    res.json(user);
}

async function status_update(req: Request, res: Response) {
    let user = await userCtrl.status_update(req);
    res.json(user);
}

// for debug
async function status_delete(req: Request, res: Response) {
    let user = await userCtrl.status_delete(req);
    res.json(user);
}

export default router;
```

今の段階では、RoutesとControllers、Modelsの機能分割を考えてないので後でリファクタリングしたい

##Controlelrs

```typescript
import bcrypt from 'bcrypt';
import User, {UserSchema} from "../models/user.model";
import { Request } from "express";
import Joi from "Joi";
var moment = require('moment-timezone');

export async function status_read(req: Request) {
    // TODO: _idを非表示になぜか出来ない
    let result = await User.find({ user_id: req.params.user_id }, {_id:0}).exec();
    return await new User(result[0]);
}

export async function status_insert(req: Request) {
    // TODO: RequestのValidate
    req.body["user_id"] = req.params.user_id;
    // TODO: Ramdom Password
    req.body["password"] = bcrypt.hashSync(req.body.password, 10);

//    delete req.body.password;
    let val = await Joi.validate(req.body, UserSchema.insertSchema, { abortEarly: true });
    return await new User(val).save();
}

export async function status_update(req: Request) {
    // TODO: RequestのValidate
    let val = await Joi.validate(req.body, UserSchema.updateSchema, { abortEarly: true });
    val["updatedAt"] = moment().tz("Asia/Tokyo").format();

 //   delete req.body["user_id"];
    console.log({$set: val });
    return await User.updateOne({user_id: req.params.user_id}, {$set: val });
}

export async function status_delete(req: Request) {
    return await User.remove({user_id: req.params.user_id});
}
```

#あとがき
Joiはとても便利。Validateが圧倒的に楽になった
bcryptはgipで実装されてるため色々エラーが出て苦労した。bcrypt-nodeというのがありそっち使えばよかったかも？
JavascriptのTimezoneはカオス
async/awaitは説明しないとダメかな
リファクタリングしたい