#フォルダのリファクタリングを行った
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;
のようなものがエラーが取れなかったので 後回しにした
const path = require('path');
const express = require('express');
const httpError = require('http-errors');
module.exports = app;
のようなコードが
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設定
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設定
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テーブルのスキーマを作る
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にルーティングされるようにした
import express from "express";
import userRoutes from './user.route';
const router = express.Router();
router.use('/user/:user_id/user', userRoutes);
export default router;
その後それぞれの機能にルーティングする
ここでは /user機能へルーティングする
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
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は説明しないとダメかな
リファクタリングしたい