1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

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

1
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?