Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

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にルーティングされるようにした

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機能へルーティングする

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

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は説明しないとダメかな
リファクタリングしたい

YukiMiyatake
C++が喋れる ゲームプログラマ インフラ、サーバ、UNITY、ゲームエンジンが最近多いな・・ MONA: MPpuEnmqDYBCxSZyG5cBDt6UWtXczmRmkn BTC: 13JpgsF3n6K2WhjEeUuUUqS7V71gWdFx56 BCH: 18q6rfi9ynyTgynrB8tJ2eSDLPQM32RZk5
http://murasame-labo.hatenablog.com/
murasame
ゲーム、エンタメ、サーバインフラ等 少人数で技術力の高い仕事をする会社
http://murasame-lab.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away