20190625のNode.jsに関する記事は22件です。

next.jsで@hapi/joiを使う

nextjsを使い始めたはいいが、SSRで結構詰まることがありますよね。
今回はバリデーションのメジャーどころ、@hapi/joiをnextjsで利用する方法を記載しておきます。

バリデーションはvalidatejsなど様々なものがありますが、
汎用性、スター数、コミュニティなどで考えると、@hapi/joiが有力候補です。

joi-browserを導入する

joiは主にnodejs用に作られており、nextjsではブラウザもレンダリングできなければ利用できません。
そこで、joi-browserを利用します。

まずは必要なライブラリを導入します。

$ yarn add @hapi/joi joi-browser

nodejs側では @hapi/joi、 front側では joi-browserを参照するためにwebpackの設定を変更します。

next.config.js
module.exports = {
  webpack: (config, { isServer }) => {
    if(isServer) {
      config.resolve.alias["@hapi/joi"] = "joi-browser"
    }

    return config
  }
}

こうすることで、フロント側でも同じ名前でインポートすることができます。

import * as Joi from '@hapi/joi'

// define your schema
const schema = {...}
const inputs = {...}

Joi.validate(inputs, schema)

フロントの場合は、nodejsのcoreモジュールを利用するバリデーションが使えなかったりするので、お気をつけください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.jsで文字列のバイト数を簡単に把握する関数

バイト数を求める

javascriptで文字列から、UTF-8でバイト数を求める方法を探してたらどのコードも大変そう、、、
たまたま見かけた、下記の関数がとても便利だったので、メモ。

Buffer.byteLength関数

第一引数に、引用元、第二引数に、文字コードの指定。

公式サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者向け]VueとExpressを使ってパラメーターの受け渡しをしてみよう

はじめまして、PMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います.

はじめに

Vue.jsでaxiosを使ってパラメーターをpostしexpressで作った、apiで受取るというところまでやりたいと思います。

必要な環境

Vue.jsの実装

  • まずこちらの記事を参考にしてサーバーを立ち上げてください。
  • http://localhost:8080にアクセスして、このような画面が表示されれば成功です。 スクリーンショット 2019-06-25 20.11.51.png
  • 次にaxiosをインストール
    • npm install axios -s

画面を作る

  • 次にsrc/components/HelloWorld.vueをいかのように書き換えてください
HelloWorld.vue
<template>
  <div class="hello">
    <form action>
      <input type="text" placeholder="text" v-model="text">
      <input type="submit" value="decide" @click="submitClick">
    </form>
  </div>
</template>

画面はこのようになっています
スクリーンショット 2019-06-25 20.36.00.png

HelloWorld.vueの</template>以下に<script>を追加

HelloWorld.vue
<script>
import Axios from "axios";
export default {
  name: "HelloWorld",
  data() {
    return {
      text: null
    };
  },
  methods: {
    submitClick() {
      alert(this.text)
    }
  }
};
</script>

このようにinputに文字を入力してその文字がalertに表示されれば成功です
スクリーンショット 2019-06-25 20.42.23.png

<script></script>を書き換え

次に<script></script>を以下のように書き換えてください

HelloWorld.vue
<script>
import Axios from "axios";
export default {
  name: "HelloWorld",
  data() {
    return {
      text: null
    };
  },
  methods: {
    submitClick() {
      const body = {
        text: this.text
      };
      Axios.get(`http://localhost:3000/hoge/${this.text}`)
        .then(res => {
          console.log(res.data.name)
        })
        .catch(err => {
          console.log(err);
        });
    }
  }
};
</script>

Expressの実装

簡単なapiを作ってみる

  • アプリ名/app.jsに以下のことを追記しましょう
app.js
app.get('/hoge', (req, res) => {
  res.json({
    name: "hoge"
  })
});

スクリーンショット 2019-06-25 21.03.04.png

apiを書き換える

app.js
app.get('/hoge/:name', (req, res) => {
  let data = req.params
  console.log(data.name);
  res.json({
    name: data.name
  })
});

これでコードを書く部分はすべて終了です。

動かしてみる

  • http://localhost:8080にアクセスし任意の文字を入力し送信してください。

  • Expressを動かしているconsole上にfrontから送信した文字が表示されていれば成功です
    スクリーンショット 2019-06-25 21.25.26.png
    スクリーンショット 2019-06-25 21.25.38.png

最後に

初心者向けにVue.jsからExpressのapiへのパラメーターの受け渡しを行いました。
これから始める方の手助けになれば幸いです。
なにか間違いがあればご連絡ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macosx の node 環境がぐちゃぐちゃになったので再インストール

備忘録で。

二つのプロジェクトでnodeを使っているのですが、'npm start'がよくわからないエラーで走らない! (先日何か brewで更新したような・・) どうも小手先の修正で直せそうなエラーではないので nodeごと再インストールしました。 (最悪 macosxのクリーンインストールまで考えたけど、macはnodeでできてるわけじゃないからそこまで必要ないだろうと)

# 一応やってみるけどこれだけでは不足だった
brew uninstall --ignore-dependencies node
# そもそも /usr/localより下を rootにならずに書き換えられるようにしておく必要あり。私のはそうなっていなかった。
sudo chown -R your_login_name:wheel /usr/local

# なんかunlinkできないとかlinkできないとかいわれるのでみつけたnode関連のフォルダは消す!
sudo rm -rf /usr/local/share/doc/node
sudo rm -rf /usr/local/include/node
sudo rm -rf /usr/local/Cellar/node/

#これでやっとクリーンインストールが通りました。
brew install node
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[node.js]Expressでパスワードのハッシュ化を行おう

はじめまして、PMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

はじめに

パスワードをデータベースに保存するときにはその情報をハッシュ化する必要があります。
なぜパスワードをハッシュ化しないといけないかなどを詳しく知りたい方はこちらを参考にしてみてください。
今回はbcryptというライブラリを使ってデータのハッシュ化を行って行きたいと思います。

実装

インストール

npm install bcrypt -s

パスワードのハッシュ化

まずExpress内の自分がデータをハッシュ化したいファイル内で以下のように記述してください

hash.js
const bcrypt = require('bcrypt');
const password = "hoge"
let hashed_password = bcrypt.hashSync(data, 10);
console.log(hash_data)

"hoge"という文字列がランダムな文字に変換されています。

ハッシュ化されたパスワードの照合

ハッシュ化されたパスワードと自分の知っているパスワードの照合を行っていきます

hash_confirm.js
const bcrypt = require('bcrypt');
bcrypt.compareSync(hash_password, "hoge") // =>ture
bcrypt.compareSync(hash_password, "fake_hoge") // =>false
  • hash_dataはもともと"hoge"をハッシュ化したものなので、bcrypt.compareSync(hash_data, "hoge")trueとなります。
  • bcrypt.compareSync(hash_data, "fake_hoge")はハッシュ化される前のデータと比較されているデータ(今回は"hoge"face_hogeを比較している)が異なるのでfalseとなる。

最後に

今回は非常に簡単なhash化の方法をご紹介しました。
もっと詳しいことが知りたい場合はこちらを参照ください。callbackやasync/awaitでエラーハンドリングを行うことも出来ます。
なにか間違いなどありましたら指摘していただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[node.js]Expressでデータのハッシュ化を行おう

はじめまして、PMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

はじめに

パスワードなどの重要な情報をデータベースに保存するときにはその情報をハッシュ化する必要があります。
なぜ重要な情報をハッシュ化しないといけないかなどを詳しく知りたい方はこちらを参考にしてみてください。
今回はbcryptというライブラリを使ってデータのハッシュ化を行って行きたいと思います。

実装

インストール

npm install bcrypt -s

データのハッシュ化

まずExpress内の自分がデータをハッシュ化したいファイル内で以下のように記述してください

hash.js
const bcrypt = require('bcrypt');
const data = "hoge"
let hash_data = bcrypt.hashSync(data, 10);
console.log(hash_data)

"hoge"という文字列がランダムな文字に変換されています。

ハッシュ化されたデータの照合

ハッシュ化されたデータと自分の知っているデータの照合を行っていきます

hash_confirm.js
const bcrypt = require('bcrypt');
bcrypt.compareSync(hash_data, "hoge") // =>ture
bcrypt.compareSync(hash_data, "fake_hoge") // =>false
  • hash_dataはもともと"hoge"をハッシュ化したものなので、bcrypt.compareSync(hash_data, "hoge")trueとなります。
  • bcrypt.compareSync(hash_data, "fake_hoge")はハッシュ化される前のデータと比較されているデータ(今回は"hoge"face_hogeを比較している)が異なるのでfalseとなる。

最後に

今回は非常に簡単なhash化の方法をご紹介しました。
もっと詳しいことが知りたい場合はこちらを参照ください。callbackやasync/awaitでエラーハンドリングを行うことも出来ます。
なにか間違いなどありましたら指摘していただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nodejsでWeb予約サイトを構築した備忘録

仕事でイベント予約のためにWebサイトを構築したので備忘録。
予約フロー自体はいたってシンプルに特に工夫した点はない。
単純にnodejsでの構築手順。

要件

  1. Linux上に構築。
  2. nodeはインストール済みである。
  3. mySQLをインストール済みである。

nodeプロジェクト生成

任意な場所で以下のコマンドをたたく。

express --view=pug [プロジェクト名]

プロジェクト名でフォルダが作られてnodejsプロジェクトのひな形ファイルが出来上がる。
次に必要なパッケージをインストール。

npm install

この時点で既に実行ができる。

npm start

これで起動してブラウザで起動ポートにアクセスするとダミー画面が表示される。
起動するポートは以下で定義。

bin/www
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('myqpp:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '8089');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);
(以下略)

の「process.env.PORT」がそうだ。

後は目的に合わせて修正していく。

プロジェクトの構造

├── app.js
├── bin
│   └── www
├── node_modules
│
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.pug
    ├── index.pug
    └── layout.pug
リソース名 内容
app.js アプリケーション定義
bin/www npm start で実行されるスクリプト。ルーティングなどを定義する
node_modules npm installでインストールされる依存パッケージ群
package.json 使用する依存パッケージとバージョンの定義
public サイトリソース。 publicフォルダがドキュメントルートとなる。
routes バックエンドスクリプト。app.jsで宣言したURIにアクセスした時の挙動を記述する。
views HTMLテンプレート。routesスクリプトからこの中のテンプレートを指定してレンダリング指示する。好みに合わせて pug/ejs/jade の形式から選べる。

ここからは実装内容を備忘録

app.js

実装した点は以下。
1. ルーティング
2. HTMLエンジンをejsに変更。

app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var fkdeventRouter = require('./routes/fkdevent');
var workshopRouter = require('./routes/workshop');
var resultRouter = require('./routes/result');
var checkinRouter = require('./routes/checkin');
var manageRouter = require('./routes/manage');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
-app.set('view engine', 'jade');
+app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

//ルーティング
+app.use('/workshop', workshopRouter);
+app.use('/yoyaku-result', resultRouter);
+app.use('/yoyaku-checkin', checkinRouter);
+app.use('/yoyaku-manage', manageRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  res.status(err.status || 404);
  res.render('err404', { error: err });
});

// error handler
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('err500', { error: err });
});
module.exports = app;

bin/www

実装した点は以下。
1. ルーティング
2. HTMLエンジンをejsに変更。
3. https有効。
4. ポートを外部定義化。

bin/www
#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
+var define = require('../define');
var debug = require('debug')('reserve-app:server');
+var https = require('https');
var fs = require('fs');
+var ssl_server_key = '/etc/letsencrypt/live/fan-technology.com/privkey.pem';
+var ssl_server_crt = '/etc/letsencrypt/live/fan-technology.com/cert.pem';
+var options = {
+  key: fs.readFileSync(ssl_server_key),
+  cert: fs.readFileSync(ssl_server_crt)
+};

/**
 * Get port from environment and store in Express.
 */

-var port = normalizePort(process.env.PORT || '3000');
+var port = normalizePort(process.env.PORT || define.PORT);
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = https.createServer(options, app);
(以下略)

define.js

以下の形で宣言すると外部から読み込んで参照できる。
統一したい部分を外出しする。

module.exports = Object.freeze({
  PORT: 8089
});

public以下

特にnodeだからでやることはない。いつも通り実装していく。

routes

実装したのは以下。
1. 画面ごとにmysqlと連携させる。
2. HTMLテンプレートを指定して画面表示。
3. メールを飛ばす。

以下要点だけ記載。

API宣言

GETならこう

router.get('/', function(req, res, next) {
・・・
}

POSTならこう

router.post('/', function(req, res, next) {
・・・
  {
    最終結果の返却
    res.end();
  }
とりあえず200を返却
res.status(200);
}

※注意点はPOSTの場合は非同期で最終結果を返す場合でも、いったん同期的には200を返して
非同期の最終結果でres.end()でクローズする。

mysqlとの連携

dbコネクション

var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'xxxxxxx',
  password : 'yyyyyyy',
  database : 'zzzzzzz'
});
connection.connect(function(err) {
  if (err) {
    console.error('error connecting: ' + err.stack);
    return;
  }
   console.log('connected as id ' + connection.threadId);
});

・・・ここで処理を行う。

用が済んだらコネクション切断
connection.end(function() {});

select

connection.query({
  sql: 'select * from table',
  timeout: 40000, // 40s
}, function (error, rows, fields) {
  value = rows[0]['value']+1;
});

update

connection.query({
  sql: 'update table set columns = true where x=?',
  timeout: 40000, // 40s
  values: [req.body.hash]
}, function (error, rows, fields) {
  if(error) {
    console.log(error);
  } else {
    console.log('done.');
  }
});

insert

var data = {
  column1: value1,
  column2: value2
};

connection.query("insert into table set ?", data, function(error,results,fields) {
  connection.end(function() {});
  if(error) {
    console.lo
  }
}

db処理の同期

クエリーが非同期で実行されるので複数の実行終了後に行いたい場合はPromiseで。

  var dbTask1 = new Promise(function(resolve, reject) {
    connection.query({
      sql: 'select * from table1',
      timeout: 40000, // 40s
    }, function (error, rows, fields) {
      value1 = rows[0]['value']+1;
      resolve();
    });
  });
  var dbTask2 = new Promise(function(resolve, reject) {
    connection.query({
      sql: 'select * from table2',
      timeout: 40000, // 40s
    }, function (error, rows, fields) {
      value2 = rows[0]['value']+1;
      resolve();
    });
  });

  Promise.all([dbTask1, dbTask2]).then(function () {
    ここに実行する処理をかく。
  }

画面レンダリング

必要な情報をjsonで渡してHTMLテンプレートを指定する。

var data = {
  data1: value1,
  data2: value2
};
res.render('[テンプレートファイル名].ejs', data);

テンプレートの中身

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta property="og:url" content="https://www.your-domain.com/your-page.html" />
  <meta property="og:type" content="website" />
  <meta property="og:title" content="Your Website Title" />
  <meta property="og:description" content="Your description" />
  <meta property="og:image" content="https://www.your-domain.com/path/image.jpg" />
  <link rel="icon" href="/images/favicon.ico" />
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
  <link rel="stylesheet" href="/css/common.css" crossorigin="anonymous">
  <link href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.7.1/css/lightbox.css" rel="stylesheet">
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
    integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
    crossorigin="anonymous"></script>
  <script src="/js/lib/jquery.js"></script>
  <script src="/js/index.js" type="text/javascript"></script>

  <title>イベントサイトトップぺージ</title>
</head>

<body>
  <input type="hidden" id="fkdeveurl" value="<%= fkdeveurl %>" />
  <input type="hidden" id="workshopurl" value="<%= workshopurl %>" />
  <div id="fb-root"></div>
  <script async defer crossorigin="anonymous"
    src="https://connect.facebook.net/ja_JP/sdk.js#xfbml=1&version=v3.2"></script>

</body>
</html>

テンプレート上で置き換えるのは以下のようにかく。

<input type="hidden" id="fkdeveurl" value="<%= fkdeveurl %>" />

if文

<% if(item2.reserve_id>0) { %>
<td style="min-width: 350px;"><span class="<%= item2.checkin %>"><a href="<%= item2.url %>"
      target="_blank"><%= item2.p_name %>(<%= item2.p_furi %>)様<br /><%= item2.c_name %>(<%= item2.c_furi %>)さん(<%= item2.gakunen %>)</a></span>
</td>
<% } else { %>
<td class="none">(空き)</td>
<% } %>

for文

<% cad_time.forEach(function (item, key) { %>
<label class="control control--radio wd100">
  <span class="<%= item.attr %> time-label"><%- item.label %></span>
  <span class="zanseki"><%= item.zanseki %></span>
  <input type="radio" name="class_time" value="<%= item.value %>" <%= item.attr %> />
  <div class="control__indicator <%= item.attr %>"></div>
</label>
<% }); %>

javascript側で画面遷移

routesのバックエンドAPIの戻り価でURLが含まれたJSONを返す。
JavaScript側でURLを変える。

バックエンド側
 最終的に返すもののみ。
  res.json({status: 0, next_url: nexturl});
  res.end();
javascript側
$.ajax({
  type:"post",
  url:"/fukudaya-event",
  data:JSON.stringify(data),
  contentType: 'application/json',
  dataType: "json",
  success: function(json_data) {
    location.href=json_data.next_url;
  },
  error: function() {
  },
  complete: function() {
  }
});

または画面レンダー時にhiddenにあらかじめ隠しセットしておきJavaScriptで取得してそのまま遷移させる方法もとれる。これだと通信が1往復減らせる。

メール送信

//メールテンプレートを開く
var mailtemplate = fs.readFileSync(require("app-root-path")+"/data/mail.txt", {encoding: "utf-8"});
var nexturl = '/yoyaku-result?key='+hash;
let time = class_time.filter(function(item, index){if (item.value == req.body.class_time) return true;})[0];
var mailbody = mailtemplate.replace("$p_name$", req.body.p_name)
  .replace("$p_kana$", req.body.p_kana)
  .replace("$c_name$", req.body.c_name)
  .replace("$c_kana$", req.body.c_kana)
  .replace("$class_name$", time.label2)
  .replace("$class_time$", time.label3)
  .replace("$url$", 'https://fan-technology.com:'+define.PORT+nexturl)

//メールの内容
var mailOptions = {
  from: 'xxxxxxx',
  to: req.body.email,
  subject: 'cccccccc',
  text: mailbody
};

//SMTPの接続
var smtp = mailer.createTransport({
  host: 'localhost',
  port: 587,
  ssl: false,
  tls:{
    rejectUnauthorized: false
  },
  use_authentication: true,
  user: "aaaaaa",
  pass: "bbbbbb"
});

//メールの送信
smtp.sendMail(mailOptions, function(err, res){
  if(err){
    console.log(err);
  }else{
    console.log('Message sent: ' + res.message);
  }
  smtp.close();
});

メールテンプレートは普通のテキストファイル。可変部分を置換して作成して送信する。

qrコード生成

var qr = require('qr-image');
var fs = require('fs');

(中略)

var sha512 = crypt.createHash('sha512');
sha512.update('[シード]');
var hash = sha512.digest('hex');

var code = qr.image(url+hash, { type: 'png'});
var qrfile = require('crypto').randomBytes(8).toString('hex')+'.png';
code.pipe(fs.createWriteStream(require("app-root-path")+'/public/qr/'+qrfile));

ここでは受付用の予約ごとのユニークなURLを生成するのにハッシュ生成も行っている。
QRコードに埋め込む情報ができたら生成してファイルに出力して再利用できるようにここではしている。

入力バリデーション

今回はjQueryのライブラリを使ってクライアント側で行った。
jQuery Validation Plugin

専用のライブラリをインクルードする。

viewsファイル側
<script src="/js/lib/jquery.validate.js"></script>
・・・

細かいところはリファレンスを見てほしいが、ざっくり以下の感じ。
submitするフォームに対してバリデートオブジェクトを当てはめる。
オブジェクトにはチェックする項目と内容、エラー時のメッセージ、
成功時のハンドラ、エラー時の表示処理を実装する。

バリデート処理
$('#mainform').validate({
  errorElement: "span",
  errorClass: "validate-err",
  rules: {
    item: {
      required: true,
      maxlength: 50
    },
    ・・・
  },
  messages: {
    item: {
      required: "保護者のお名前を入力してください。",
      maxlength: "50文字以内で入力してください。"
    },
    ・・・
  },
  submitHandler: function() {
    submitReserve();
  },
  invalidHandler: function(event, validator) {
    for(key in validator.errorMap) {
      console.log(key);
      $('#'+key).addClass('validate-err-input');
    }
  },
  errorPlacement: function (err, element) {
    switch(element.attr("name")) {
    case "item":
      err.insertAfter("#time-label");
      break;
    default:
      element.after(err);
      break;
    }
  }
}); 

スマートなアラートウィンドウ

デザインとアニメーションに心打たれて、これを使った。
sweetalert

専用のライブラリをインクルードする。

viewsファイル側
  <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
・・・
アラート表示処理
成功時ダイアログ
swal("受付完了", json_data.message, "success");

エラー時アラート
swal("エラー発生", "申し訳ありません。時間をおいてから再度行ってください。", "error");

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsとExpressを使って画像アップロードをしてみる

はじめまして、PMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

はじめに

Vue.jsからリクエストを投げてExpressのpublicディレクトリに画像をアップロードしてみたいとおもいます。
今回画像アップロードにはmulterというライブラリを使って行きます。

実装

使用するライブラリのインストール

  • axios
    • npm install axios -s Vueプロジェクトの中でinstallしてください
  • multer
    • npm install multer -s Expressプロジェクトの中でinstallしてください

Vueの実装

まず一つvueファイルを作ってください

Uplord.vue
<template>
  <div>
    <form>
      <input type="file" id="file" v-on:change="onFileChange">
      <input type="submit" value="decide" @click="submitClick">
    </form>
  </div>
</template>
<script>
import Axios from "axios";
export default {
    data() {
        return {
            imageFile: null
        };
    },
    methods: {
        //画像が選択されたとき呼ばれる
        onFileChange(e) {
            this.imageFile = e.target.files || e.dataTransfer.files;
        },
        //submitされたときに呼ばれる
        async submitClick() {
            try {
                const formData = new FormData();
                formData.append("file", this.imageFile[0]);
                const config = {
                    headers: {
                        "content-type": "multipart/form-data",
                    }
                };
                let res = await Axios.post("/image", formData, config);
                console.log(res);
                if (res.data.status === "error") {
                    alert(res.data.error);
                } else {
                    alert("登録完了")
                }
            } catch (error) {
                alert("画像の送信に失敗しました");
            }
        }
    }
}
</script>
  • ここではformDataを使って画像をpostしています。
  • formData.append("key",value)を使うことでkey:valueを対応させる形で任意のデータを乗せることが出来ます。

注意点

  • valueにはオブジェクトなどは乗せることが出来ません。基本的にkeyとvalueを一つずつ載せてください。
  • formDataに載せたvalueはStingになってしまいます。

Expressの実装

src/index.jsに以下を書いていきましょう

index.js
const express = require('express')
const multer = require('multer');
const app = express()
const storage = multer.diskStorage({
    // ファイルの保存先を指定(今回はsrc/public/image以下に画像を保存)
    destination: function (req, file, cb) {
        cb(null, 'src/public/image')
    },
    // ファイル名を指定(オリジナルのファイル名を指定)
    filename: function (req, file, cb) {
        // Math.random().toString(36).slice(-9)で乱数を生成
        const imageName = `${Math.random().toString(36).slice(-9)}_${Date.now()}.png`
        cb(null, imageName)
    }
})

const upload = multer({
    storage: storage
}).single('file')

app.post('/image', (req, res) => {
    upload(req, res, (err) => {
        if (err) {
            //アップロード失敗した場合
            res.json({
                status: "error",
                error: "fail to uplord image"
            })
        } else {
            //アップロード成功した場合
            res.json({
                status: "sucess",
                // ファイル名を返す
                path: res.req.file.filename
            })
        }
    })
});
  • const storageで画像の保存先をファイル名を指定
  • ここではファイル名を乱数と時間を組み合わせていますがこれではユニークとは言えません。ユニークにしたい方はuserIdと時間を組み合わせるなどしてください。
  • /imageのエンドポイントでuplordを呼び出しています。ただアップロードするだけでしたらもっと簡単に出来ます。(詳しくはこちら)しかしここではアップロード出来たかどうかのエラーハンドリングを行いたいのでこのような形になっています。
  • アップロードされた画像のファイル名はres.req.file.filenameで取得する事ができます。
  • 無事フロントでres.data.statussucsessになっていれば登録完了です。

最後に

multeraxiosでお手軽にVueからExpressに画像をアップロードすることが出来ました。
機会があれば是非試してみてください
なにか間違いなどありましたら教えていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[node.js] Express,Mysql2/promiseでqueryをsaync await使って簡潔に書く

はじめまして、普段はproduct managerをやっているtatsukenと申します。
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

はじめに

ExpressでMysqlを使用する際Mysql2をしようすることがあると思いますが、関連するテーブルが多くなれば多くなるほどqueryの中にqueryを書くと言うようにネストが深くなっていき、どんどん可読性が下がってしまいます。
そのような問題を解決するためにMysql2/promiseが用意されています。

Mysql2/promise使わないずコールバックを使用する場合

sample.js
const express = require('express')
const mysql = require('mysql2');
const app = express()
const db_setting = {
    host: 'db',
    user: 'hoge',
    password: 'hoge',
    database: 'hoge',
}
app.get('/', (req, res) => {
    const connection = mysql.createConnection(db_setting)
    connection.connect()
    connection.beginTransaction(function (err) {
        if (err) {
            connection.rollback(function () {
                throw err;
            });
            res.json({
                status: "error",
                error: "fail to uplord data"
            })
            connection.end()
            return
        }
        const hoge1 = {
            name: "hoge1",
        }
        connection.query('insert into table_name set ?', hoge1, (err, rows) => {
            if (err) {
                connection.rollback(function () {
                    throw err;
                });
                res.json({
                    status: "error",
                    error: "fail to uplord data"
                })
                connection.end()
            } else {
                const hoge2 = {
                    name: "hoge2"
                    insertId: rows.insertId,
                }
                connection.query('insert into table_name set ?', hoge2, (err, rows,) => {
                    if (err) {
                        connection.rollback(function () {
                            throw err;
                        });
                        res.json({
                            status: "error",
                            error: "fail to uplord data"
                        })
                        connection.end()
                    } else {
                        connection.commit(function (err) {
                            if (err) {
                                connection.rollback(function () {
                                    throw err;
                                });
                                connection.end()
                                return
                            }
                            res.json({
                                status: "success",
                                id: rows.insertId
                            })
                            connection.end()
                        });
                    }
                })
            }
        })
    })
});

ここではコールバックを使用しているのでネストの深さやコード量のせいで可読性が下がっています。

解決策

Mysql2/promiseを使いasync awaitで書く

sample.js
const express = require('express');
const app = express.Router();
const mysql = require('mysql2/promise');
app.get('/', (req, res) => {
    const hoge1 = {
        name: "hoge1",
    }
    let connection
    try {
        connection = await mysql.createConnection(db_setting)
        await connection.beginTransaction();
        const [row1] = await connection.query('insert into table_name set ?', hoge1);
        const hoge2 = {
            name: "hoge2"
            insertId: row1.insertId,
        }
        const [row2] = await connection.query('insert into table_name set ?', hoge2);
        await connection.commit();
        res.json({
            status: "success",
            id: row2.insertId
        });
    } catch (err) {
        await connection.rollback();
        res.json({
            status: "error",
            error: "fail to uplord data"
        })
    } finally {
        connection.end()
        return
    }

});

このようにasync awaitを使うことで大幅にコード量を減らすことができ、ネストが深くなるという問題も解決する事が出来ました。

注意点

  • mysql2をインポートするだけではasync awaitを使うことは出来ません。
  • mysql2/promiseを使う際はasync awaitを使ってください。
  • mysql2/promiseを使うとコールバックは認識されなくなります。

最後に

mysql2/promiseを使うと非常に簡潔にコードを書くことが出来ます。機会があればぜひ使って見てください。
なにか間違いなどあれば、教えていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[node.js] Express,Mysql2/promiseでqueryをasync/await使って簡潔に書く

はじめまして、普段はPMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

はじめに

ExpressでMysqlを使用する際Mysql2をしようすることがあると思いますが、関連するテーブルが多くなれば多くなるほどqueryの中にqueryを書くと言うようにネストが深くなっていき、どんどん可読性が下がってしまいます。
そのような問題を解決するためにMysql2/promiseが用意されています。

Mysql2/promise使わないずコールバックを使用する場合

sample.js
const express = require('express')
const mysql = require('mysql2');
const app = express()
const db_setting = {
    host: 'db',
    user: 'hoge',
    password: 'hoge',
    database: 'hoge',
}
app.get('/', (req, res) => {
    const connection = mysql.createConnection(db_setting)
    connection.connect()
    connection.beginTransaction(function (err) {
        if (err) {
            connection.rollback(function () {
                throw err;
            });
            res.json({
                status: "error",
                error: "fail to uplord data"
            })
            connection.end()
            return
        }
        const hoge1 = {
            name: "hoge1",
        }
        connection.query('insert into table_name set ?', hoge1, (err, rows) => {
            if (err) {
                connection.rollback(function () {
                    throw err;
                });
                res.json({
                    status: "error",
                    error: "fail to uplord data"
                })
                connection.end()
            } else {
                const hoge2 = {
                    name: "hoge2"
                    insertId: rows.insertId,
                }
                connection.query('insert into table_name set ?', hoge2, (err, rows,) => {
                    if (err) {
                        connection.rollback(function () {
                            throw err;
                        });
                        res.json({
                            status: "error",
                            error: "fail to uplord data"
                        })
                        connection.end()
                    } else {
                        connection.commit(function (err) {
                            if (err) {
                                connection.rollback(function () {
                                    throw err;
                                });
                                connection.end()
                                return
                            }
                            res.json({
                                status: "success",
                                id: rows.insertId
                            })
                            connection.end()
                        });
                    }
                })
            }
        })
    })
});

ここではコールバックを使用しているのでネストの深さやコード量のせいで可読性が下がっています。

解決策

Mysql2/promiseを使いasync/awaitで書く

sample.js
const express = require('express');
const app = express.Router();
const mysql = require('mysql2/promise');
app.get('/', (req, res) => {
    const hoge1 = {
        name: "hoge1",
    }
    let connection
    try {
        connection = await mysql.createConnection(db_setting)
        await connection.beginTransaction();
        const [row1] = await connection.query('insert into table_name set ?', hoge1);
        const hoge2 = {
            name: "hoge2"
            insertId: row1.insertId,
        }
        const [row2] = await connection.query('insert into table_name set ?', hoge2);
        await connection.commit();
        res.json({
            status: "success",
            id: row2.insertId
        });
    } catch (err) {
        await connection.rollback();
        res.json({
            status: "error",
            error: "fail to uplord data"
        })
    } finally {
        connection.end()
        return
    }

});

このようにasync awaitを使うことで大幅にコード量を減らすことができ、ネストが深くなるという問題も解決する事が出来ました。

注意点

  • mysql2をインポートするだけではasync/awaitを使うことは出来ません。
  • mysql2/promiseを使う際はasync/awaitを使ってください。
  • mysql2/promiseを使うとコールバックは認識されなくなります。

最後に

mysql2/promiseを使うと非常に簡潔にコードを書くことが出来ます。機会があればぜひ使って見てください。
なにか間違いなどあれば、教えていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[node.js] Express,mysql2/promiseでqueryをasync/await使って簡潔に書く

はじめまして、普段はPMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

はじめに

ExpressでMysqlを使用する際Mysql2をしようすることがあると思いますが、関連するテーブルが多くなれば多くなるほどqueryの中にqueryを書くと言うようにネストが深くなっていき、どんどん可読性が下がってしまいます。
そのような問題を解決するためにMysql2/promiseが用意されています。

Mysql2/promise使わずコールバックを使用する場合

sample.js
const express = require('express')
const mysql = require('mysql2');
const app = express()
const db_setting = {
    host: 'db',
    user: 'hoge',
    password: 'hoge',
    database: 'hoge',
}
app.get('/', (req, res) => {
    const connection = mysql.createConnection(db_setting)
    connection.connect()
    connection.beginTransaction(function (err) {
        if (err) {
            connection.rollback(function () {
                throw err;
            });
            res.json({
                status: "error",
                error: "fail to uplord data"
            })
            connection.end()
            return
        }
        const hoge1 = {
            name: "hoge1",
        }
        connection.query('insert into table_name set ?', hoge1, (err, rows) => {
            if (err) {
                connection.rollback(function () {
                    throw err;
                });
                res.json({
                    status: "error",
                    error: "fail to uplord data"
                })
                connection.end()
            } else {
                const hoge2 = {
                    name: "hoge2"
                    insertId: rows.insertId,
                }
                connection.query('insert into table_name set ?', hoge2, (err, rows,) => {
                    if (err) {
                        connection.rollback(function () {
                            throw err;
                        });
                        res.json({
                            status: "error",
                            error: "fail to uplord data"
                        })
                        connection.end()
                    } else {
                        connection.commit(function (err) {
                            if (err) {
                                connection.rollback(function () {
                                    throw err;
                                });
                                connection.end()
                                return
                            }
                            res.json({
                                status: "success",
                                id: rows.insertId
                            })
                            connection.end()
                        });
                    }
                })
            }
        })
    })
});

ここではコールバックを使用しているのでネストの深さやコード量のせいで可読性が下がっています。

解決策

Mysql2/promiseを使いasync/awaitで書く

sample.js
const express = require('express');
const app = express.Router();
const mysql = require('mysql2/promise');
app.get('/', (req, res) => {
    const hoge1 = {
        name: "hoge1",
    }
    let connection
    try {
        connection = await mysql.createConnection(db_setting)
        await connection.beginTransaction();
        const [row1] = await connection.query('insert into table_name set ?', hoge1);
        const hoge2 = {
            name: "hoge2"
            insertId: row1.insertId,
        }
        const [row2] = await connection.query('insert into table_name set ?', hoge2);
        await connection.commit();
        res.json({
            status: "success",
            id: row2.insertId
        });
    } catch (err) {
        await connection.rollback();
        res.json({
            status: "error",
            error: "fail to uplord data"
        })
    } finally {
        connection.end()
        return
    }

});

このようにasync awaitを使うことで大幅にコード量を減らすことができ、ネストが深くなるという問題も解決する事が出来ました。

注意点

  • mysql2をインポートするだけではasync/awaitを使うことは出来ません。
  • mysql2/promiseを使う際はasync/awaitを使ってください。
  • mysql2/promiseを使うとコールバックは認識されなくなります。

最後に

mysql2/promiseを使うと非常に簡潔にコードを書くことが出来ます。機会があればぜひ使って見てください。
なにか間違いなどあれば、教えていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[node.js] Express,mysql2/promiseでasync/await使って簡潔に書く

はじめまして、普段はPMをやっているtatsukenと申します。はじめまして
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

はじめに

ExpressでMysqlを使用する際Mysql2をしようすることがあると思いますが、関連するテーブルが多くなれば多くなるほどqueryの中にqueryを書くと言うようにネストが深くなっていき、どんどん可読性が下がってしまいます。
そのような問題を解決するためにMysql2/promiseが用意されています。

Mysql2/promise使わずコールバックを使用する場合

sample.js
const express = require('express')
const mysql = require('mysql2');
const app = express()
const db_setting = {
    host: 'db',
    user: 'hoge',
    password: 'hoge',
    database: 'hoge',
}
app.get('/', (req, res) => {
    const connection = mysql.createConnection(db_setting)
    connection.connect()
    connection.beginTransaction(function (err) {
        if (err) {
            connection.rollback(function () {
                throw err;
            });
            res.json({
                status: "error",
                error: "fail to uplord data"
            })
            connection.end()
            return
        }
        const hoge1 = {
            name: "hoge1",
        }
        connection.query('insert into table_name set ?', hoge1, (err, rows) => {
            if (err) {
                connection.rollback(function () {
                    throw err;
                });
                res.json({
                    status: "error",
                    error: "fail to uplord data"
                })
                connection.end()
            } else {
                const hoge2 = {
                    name: "hoge2"
                    insertId: rows.insertId,
                }
                connection.query('insert into table_name set ?', hoge2, (err, rows,) => {
                    if (err) {
                        connection.rollback(function () {
                            throw err;
                        });
                        res.json({
                            status: "error",
                            error: "fail to uplord data"
                        })
                        connection.end()
                    } else {
                        connection.commit(function (err) {
                            if (err) {
                                connection.rollback(function () {
                                    throw err;
                                });
                                connection.end()
                                return
                            }
                            res.json({
                                status: "success",
                                id: rows.insertId
                            })
                            connection.end()
                        });
                    }
                })
            }
        })
    })
});

ここではコールバックを使用しているのでネストの深さやコード量のせいで可読性が下がっています。

解決策

Mysql2/promiseを使いasync/awaitで書く

sample.js
const express = require('express');
const app = express.Router();
const mysql = require('mysql2/promise');
app.get('/', (req, res) => {
    const hoge1 = {
        name: "hoge1",
    }
    let connection
    try {
        connection = await mysql.createConnection(db_setting)
        await connection.beginTransaction();
        const [row1] = await connection.query('insert into table_name set ?', hoge1);
        const hoge2 = {
            name: "hoge2"
            insertId: row1.insertId,
        }
        const [row2] = await connection.query('insert into table_name set ?', hoge2);
        await connection.commit();
        res.json({
            status: "success",
            id: row2.insertId
        });
    } catch (err) {
        await connection.rollback();
        res.json({
            status: "error",
            error: "fail to uplord data"
        })
    } finally {
        connection.end()
        return
    }

});

このようにasync awaitを使うことで大幅にコード量を減らすことができ、ネストが深くなるという問題も解決する事が出来ました。

注意点

  • mysql2をインポートするだけではasync/awaitを使うことは出来ません。
  • mysql2/promiseを使う際はasync/awaitを使ってください。
  • mysql2/promiseを使うとコールバックは認識されなくなります。

最後に

mysql2/promiseを使うと非常に簡潔にコードを書くことが出来ます。機会があればぜひ使って見てください。
なにか間違いなどあれば、教えていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでCLIツールを作る

Firebase initとかcreate-react-appとかめちゃくちゃ便利だなーと思ったのと、CLIツールってどうやって作られてるのか知らなかったため今日はcreate-react-appを参考にCLIで自分のやりたいことができるようになったのでその共有です。

create-react-appというReactの開発環境を一瞬で作れるCLIツールが有るのですがそちらのpackage.jsonを見てみます。

create-react-app

https://github.com/facebook/create-react-app/blob/56d1de274576f8e5371d83f07a87c32473c4ae9a/package.json#L21-L40

この中のパッケージを使うっているのかな(puppeteerもあるんだけどこれどこで使ってるんだろう)

meow

https://github.com/sindresorhus/meow
CLIとのやり取りをするときに便利になるツール

react-dev-utilsというものがよく使われていて、基本的なCLIとのやり取りはここで行われていそうreact-dev-utilsはlernaでモノレポ管理されているみたいCLIとのやり取りはこのcross-spawnでやり取りしてそう

node-cross-spawn

https://github.com/moxystudio/node-cross-spawn

firebase-tools

firebase init

https://github.com/firebase/firebase-tools/blob/e36933b8fb65d4f59cac491fff4a4f77233c09b4/src/commands/init.js

promptからrequireしたソースコードを使用している、60行ほどのソースコードでpromptクラスとpromptOnceという関数の2つをエクスポートしている

https://github.com/firebase/firebase-tools/blob/e36933b8fb65d4f59cac491fff4a4f77233c09b4/src/prompt.ts

Inquirer

https://github.com/SBoudrias/Inquirer.js/
対話型のシェルを作るためのライブラリ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsでCLIツールを作る際に参考になるコード(create-react-appとfirebase-toolsから)

Firebase initとかcreate-react-appとかめちゃくちゃ便利だなーと思ったのと、CLIツールってどうやって作られてるのか知らなかったため今日はcreate-react-appを参考にCLIで自分のやりたいことができるようになったのでその共有です。

create-react-appというReactの開発環境を一瞬で作れるCLIツールが有るのですがそちらのpackage.jsonを見てみます。

create-react-app

https://github.com/facebook/create-react-app/blob/56d1de274576f8e5371d83f07a87c32473c4ae9a/package.json#L21-L40

この中のパッケージを使うっているのかな(puppeteerもあるんだけどこれどこで使ってるんだろう)

meow

https://github.com/sindresorhus/meow
CLIとのやり取りをするときに便利になるツール

react-dev-utilsというものがよく使われていて、基本的なCLIとのやり取りはここで行われていそうreact-dev-utilsはlernaでモノレポ管理されているみたいCLIとのやり取りはこのcross-spawnでやり取りしてそう

node-cross-spawn

https://github.com/moxystudio/node-cross-spawn

firebase-tools

firebase init

https://github.com/firebase/firebase-tools/blob/e36933b8fb65d4f59cac491fff4a4f77233c09b4/src/commands/init.js

promptからrequireしたソースコードを使用している、60行ほどのソースコードでpromptクラスとpromptOnceという関数の2つをエクスポートしている

https://github.com/firebase/firebase-tools/blob/e36933b8fb65d4f59cac491fff4a4f77233c09b4/src/prompt.ts

Inquirer

https://github.com/SBoudrias/Inquirer.js/
対話型のシェルを作るためのライブラリ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった。

@yushimatenjinです。PlayCanvasのコードエディターに付属されているリンターが、JSHintという古いものでなおかつカスタマイズができなかったので、gulp-playcanvas playcanvas-node playcanvas-cliと3つのライブラリを作りました。gulpのpluginを作ったのでその知見の共有をさせていただきます。

問題

PlayCanvasを使う上で気になった点が付属のコードエディターのLintが古く具体的には
- constを使うと黄色くなる
- セミコロンを入れないと、赤くエラーが出る
この状況だったので自分のエディターを使って開発をしたい欲が出てきました。

Image 37

作ったプラグイン

gulp-playcanvas

npmのパッケージとして公開いたしましたので下記のコマンドでインストールできます。

yarn add gulp-playcanvas

ローカル側のgulpfile.jsはwatchをして、トランスパイルしたものをPlayCanvasにREST APIを使用してアップロードしています。

examples - hot-reload

gulpfile.jsファイル

const gulp = require("gulp");
const playcanvas = require("gulp-playcanvas");
const pcOptions = require("./playcanvas.json");
const pug = require("gulp-pug");
const sass = require("gulp-sass");

gulp.task("pug", () => {
  return gulp
    .src(["src/**/*.pug", "!src/**/_*.pug"])
    .pipe(pug())
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("js", () => {
  return gulp
    .src(["src/**/*.js", "!src/**/_*.js"])
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("sass", () => {
  return gulp
    .src("src/**/*.+(scss|sass)")
    .pipe(sass())
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("watch", function() {
  gulp.watch(["src/**/*.pug", "!src/**/_*.pug"], gulp.task("pug"));
  gulp.watch(["src/**/*.js", "!src/**/_*.js"], gulp.task("js"));
  gulp.watch("src/**/*.+(scss|sass)", gulp.task("sass"));
});
gulp.task("default", gulp.parallel("watch"));

パッケージのインストール後、playcanvas.jsonという設定ファイルを同じディレクトリに置き、yarn gulpコマンドで監視対象をwatchしておきファイルの中身に変更があった場合に同期をする形で動きます。

playcanvas.json
{
  "accessToken": "",
  "scenes": [],
  "projectId": "",
  "branchId": "",
  "projectName": "",
  "remotePath": ""
}

動かした結果

Hot Reload + ローカルのeslint + prettierで開発ができるのでかなり快適になりました。

タスクは4つの動きをします。

1. 監視

コードの変更をwatch ソースコードの変更を検知しタスクを実行

gulp.task("watch", function() {
  gulp.watch(["src/**/*.pug", "!src/**/_*.pug"], gulp.task("pug"));
  gulp.watch(["src/**/*.js", "!src/**/_*.js"], gulp.task("js"));
  gulp.watch("src/**/*.+(scss|sass)", gulp.task("sass"));
});

この部分ですね、pug, js, sassに変更があった場合にそれぞれのタスクを実行します。

2. ビルド & 3. アップロード

PugやSassなどをHTML, CSSへ変換し、REST APIを使用しビルドしたコードをアップロードします

const pug = require("gulp-pug");
/*
gulp.task("pug", () => {
  return gulp
    .src(["src/**/*.pug", "!src/**/_*.pug"])
    .pipe(pug())
    .pipe(gulp.dest("dist/"))
------------自作のプラグインを実行------------------
    .pipe(playcanvas(pcOptions));
------------自作のプラグインを実行--------------------
});
*/

https://github.com/yushimatenjin/gulp-playcanvas/blob/master/index.js#L1

Gulpのプラグインの中身はストリームで流れてくるファイルの情報を使用してアップロードしています。
オプションの渡し方などは、gulp-ftpを参考にしました。

const PlayCanvas = require("playcanvas-node").default;
const through = require("through2");
const gutil = require("gulp-util");
const path = require("path");

module.exports = options =>
  through.obj(function(file, enc, callback) {
    if (file.isNull()) {
      return callback(null, file);
    }

    if (file.isStream()) {
      return cb(
        new gutil.PluginError("gulp-playcanvas", "Streaming not supported")
      );
    }
    const playcanvas = new PlayCanvas(options);
    playcanvas.updateAssets(
      options.remotePath,
      path.basename(file.path),
      file.path
    );

    return callback(null, file);
  });

1行目でplaycanvas-nodeという自作のライブラリを読み込んでいるのですが、このライブラリを作ったことで、APIとのやり取りをシンプルに書けてとてもよかた、REST APIのラッパーを書いておくのはめちゃめちゃ良いという知見がたまりました。

参考リンク

PlayCanvas.jp
PlayCanvas/Engine
playcanvas-node
playcanvas-gulp

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gulpのプラグインを書いたらPlayCanvasでの開発がめちゃくちゃ便利になった

@yushimatenjinです。PlayCanvasのコードエディターに付属されているリンターが、JSHintという古いものでなおかつカスタマイズができなかったので、gulp-playcanvas playcanvas-node playcanvas-cliと3つのライブラリを作りました。gulpのpluginを作ったのでその知見の共有をさせていただきます。

問題

PlayCanvasを使う上で気になった点が付属のコードエディターのLintが古く具体的には
- constを使うと黄色くなる
- セミコロンを入れないと、赤くエラーが出る
この状況だったので自分のエディターを使って開発をしたい欲が出てきました。

Image 37

作ったプラグイン

gulp-playcanvas

npmのパッケージとして公開いたしましたので下記のコマンドでインストールできます。

yarn add gulp-playcanvas

ローカル側のgulpfile.jsはwatchをして、トランスパイルしたものをPlayCanvasにREST APIを使用してアップロードしています。

examples - hot-reload

gulpfile.jsファイル

const gulp = require("gulp");
const playcanvas = require("gulp-playcanvas");
const pcOptions = require("./playcanvas.json");
const pug = require("gulp-pug");
const sass = require("gulp-sass");

gulp.task("pug", () => {
  return gulp
    .src(["src/**/*.pug", "!src/**/_*.pug"])
    .pipe(pug())
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("js", () => {
  return gulp
    .src(["src/**/*.js", "!src/**/_*.js"])
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("sass", () => {
  return gulp
    .src("src/**/*.+(scss|sass)")
    .pipe(sass())
    .pipe(gulp.dest("dist/"))
    .pipe(playcanvas(pcOptions));
});

gulp.task("watch", function() {
  gulp.watch(["src/**/*.pug", "!src/**/_*.pug"], gulp.task("pug"));
  gulp.watch(["src/**/*.js", "!src/**/_*.js"], gulp.task("js"));
  gulp.watch("src/**/*.+(scss|sass)", gulp.task("sass"));
});
gulp.task("default", gulp.parallel("watch"));

パッケージのインストール後、playcanvas.jsonという設定ファイルを同じディレクトリに置き、yarn gulpコマンドで監視対象をwatchしておきファイルの中身に変更があった場合に同期をする形で動きます。

playcanvas.json
{
  "accessToken": "",
  "scenes": [],
  "projectId": "",
  "branchId": "",
  "projectName": "",
  "remotePath": ""
}

動かした結果

Hot Reload + ローカルのeslint + prettierで開発ができるのでかなり快適になりました。

タスクは4つの動きをします。

1. 監視

コードの変更をwatch ソースコードの変更を検知しタスクを実行

gulp.task("watch", function() {
  gulp.watch(["src/**/*.pug", "!src/**/_*.pug"], gulp.task("pug"));
  gulp.watch(["src/**/*.js", "!src/**/_*.js"], gulp.task("js"));
  gulp.watch("src/**/*.+(scss|sass)", gulp.task("sass"));
});

この部分ですね、pug, js, sassに変更があった場合にそれぞれのタスクを実行します。

2. ビルド & 3. アップロード

PugやSassなどをHTML, CSSへ変換し、REST APIを使用しビルドしたコードをアップロードします

const pug = require("gulp-pug");
/*
gulp.task("pug", () => {
  return gulp
    .src(["src/**/*.pug", "!src/**/_*.pug"])
    .pipe(pug())
    .pipe(gulp.dest("dist/"))
------------自作のプラグインを実行------------------
    .pipe(playcanvas(pcOptions));
------------自作のプラグインを実行--------------------
});
*/

https://github.com/yushimatenjin/gulp-playcanvas/blob/master/index.js#L1

Gulpのプラグインの中身はストリームで流れてくるファイルの情報を使用してアップロードしています。
オプションの渡し方などは、gulp-ftpを参考にしました。

const PlayCanvas = require("playcanvas-node").default;
const through = require("through2");
const gutil = require("gulp-util");
const path = require("path");

module.exports = options =>
  through.obj(function(file, enc, callback) {
    if (file.isNull()) {
      return callback(null, file);
    }

    if (file.isStream()) {
      return cb(
        new gutil.PluginError("gulp-playcanvas", "Streaming not supported")
      );
    }
    const playcanvas = new PlayCanvas(options);
    playcanvas.updateAssets(
      options.remotePath,
      path.basename(file.path),
      file.path
    );

    return callback(null, file);
  });

1行目でplaycanvas-nodeという自作のライブラリを読み込んでいるのですが、このライブラリを作ったことで、APIとのやり取りをシンプルに書けてとてもよかた、REST APIのラッパーを書いておくのはめちゃめちゃ良いという知見がたまりました。

参考リンク

PlayCanvas.jp
PlayCanvas/Engine
playcanvas-node
playcanvas-gulp

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebSocket の検証では wscat が便利だった

WebSocket の動作確認に wscat が便利すぎる件

WebSocketに対応したCloudFrontをwscatで疎通確認してみた

既に記事がありましたが、自分メモ。

どんな時に使う?

WebSocket の接続試験を行う時に、いちいちサーバー・クライアント側でプログラムを書くの面倒だなーと思っていたら、調べてみた所 wscat という便利な npm があったので使ってみました。

インストール方法

Node.js が入っていれば npm コマンドでインストール可能。

$npm install -g wscat

Node.js がまだ入っていない場合には各 OS などに併せてインストールしておいて下さい

サーバー

-l オプションを指定してコマンドを実行することで指定したポートで WebSocket を受け付ける(listen)事ができます。
以下は 8000番ポートで受付を行う際の例

$wscat -l 8000

クライアント

サーバー側が HTTP の場合、wsスキームを使い、HTTPS の場合には wssスキームを利用する必要があります。

Difference between ws and wss?

# HTTP
$wscat -c ws://example.com:8000

# HTTPS
$wscat -c wss://example.com:443

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】NPM・package.jsonを理解する

概要

npm を初めて扱うときは、パッケージをどうやってインストール・インポートするのか、package.json がどういう役割をもっているのかなど分からないことだらけであり、筆者も少しずつ調べては試すことを繰り返した記憶がある。これから Node.js を学ぼうという人には npm の使い方でつまづいてほしくないため、この記事では npm を使う上で必要な概念的知識を説明する。この記事をすべて読めばスムーズに開発が始められると思われる。

NPM とは

NPM と名のつくものは実は 2 つあり、ひとつはオンライン上のパッケージレジストリ、つまり世界中の開発者が作った Node.js のパッケージが集められた場所である。もう一つは Node.js に付属している、パッケージを操作するための CLI(コマンドラインインターフェイス; コマンドラインから実行できるプログラム)である。

以降、曖昧さを回避するためにパッケージレジストリの方を大文字のNPM、CLI の方を小文字のnpmで表記する。

NPM(レジストリ)

NPM は現時点で他の言語のものを含め世界最大のパッケージレジストリであり、ブラウザ用のライブラリ、Node.js 用のライブラリを含めて多種多様のものがある。NPM に登録されているパッケージは公式サイトでブラウジング、検索できる。

パッケージを利用する点で注意すべきなのは、誰でもパッケージを公開できるため安全であるという保証はないということである1。特に業務レベルでパッケージを使う際には、その dependency(そのパッケージが使用しているまた別のパッケージ)を含めて本当に信頼できるかを吟味する必要がある。しかし React などのある程度高機能なライブラリになると、dependency の dependency...とたどっていくと膨大なパッケージ数となり、とても手作業でチェックできるものではないという問題がある。

Note: ちなみにほとんどのパッケージは GitHub にソースコードが載せられているが、そのコードがそのまま NPM に登録されているという保証はどこにもなく、コードをチェックしたい場合は NPM から直接ダウンロードするべきである。

npm(CLI)

npm は Node.js プロジェクトの作成、dependency の追加・削除・アップデート、スクリプトの実行、パッケージの公開などを行うことができる。

パッケージ

パッケージとはプログラムのまとまりであり、NPM で公開されているほとんどのパッケージは外から使うためのライブラリである。世の中にあるパッケージを使えば自分で一からコードを書かなくとも高度な機能を実現することができる。

パッケージを利用するためには、直接パッケージをダウンロードして自分のプロジェクトに含めればよいのではないか、さらには Git のリポジトリに含めてよいのではないかと思うかもしれない。しかし、もしそのパッケージに新しいバージョンが出て、バグ修正や機能の追加がされたとき、自分のプロジェクト内のパッケージもアップデートしたくなるかもしれない。そうすると自分のプロジェクト内のファイルを手動で更新しなければならなくなる。シェルを使えば簡単に更新できるかもしれないが、全く同じコード(正確には同期されるべきコード)が複数の場所(今の場合 NPM と自分の Git リポジトリ)で管理されるというのは無駄であり、「本当に自分のプロジェクトに含まれているパッケージは世に公開されているパッケージと内容が一致しているのか」という懸念も生じる。

よって外部のパッケージは自分のプロジェクトに含めるのではなく、「このプロジェクトは NPM のこのパッケージに依存している」、という依存情報だけを「宣言」するのがよいということに落ち着く。このような依存先のパッケージをこの界隈の言葉で dependency(depend=依存する)と呼ぶ。NPM ではパッケージは別のパッケージに依存し、そのパッケージがまた別のパッケージに依存し...と、パッケージが dependency のネットワークを成すことになる。これは Maven、pip といった他の言語のパッケージレジストリについても同じである。例えば express というパッケージの dependency tree は下の図のようになっている。

npm-network-express.png

プロジェクト=パッケージ

通常 Node.js のプロジェクトは npm のパッケージ 1 個に対応する。つまり、プロジェクトを 1 つ開発するということは、公開するしないにかかわらずパッケージを 1 つ作り上げるということに相当する。よって Node.js で開発するためには、パッケージというものがどういう構造になっているのかを理解する必要がある。

npm にとってパッケージというのはpackage.jsonというファイルの親ディレクトリに含まれるファイル群である。例えばディレクトリ~/projects/my-project/package.jsonがあれば、~/projects/my-project/がそのプロジェクトのルートディレクトリ(一番根っこのディレクトリ)となる。npm のコマンドは常にルートディレクトリで実行することになる。

依存パッケージを自分のパッケージに含めないと先に述べたが、実際には依存パッケージのファイルをローカルのどこかにダウンロードする必要がある。npm でパッケージをインストールすると、それらはルートディレクトリ直下のnode_modulesディレクトリにあくまで仮置場としてダウンロードされる。よってこのディレクトリは.gitignoreで Git の repo から除外するのが普通であり、このディレクトリ内のファイルは編集してはいけない

以上のファイル、ディレクトリ以外についてはどのようにファイルをおいてもよい(当然、使用するフレームワークなどによってはファイルの配置に制約を受けることはある)。

パッケージの作成

Note: 以降、Node.js と npm(>=v6)が正常にインストールされておりそれぞれコマンドラインでnodenpmという名前でアクセスできる(PATH に登録されている)ことを前提とする。

プロジェクト、すなわちパッケージを一から作成するにはまずpackage.jsonを作成することから始まる(例えば React のようにプロジェクトを生成する CLI パッケージが用意されている場合はそれを用いればよい)。以下を実行すれば、パッケージ名などがインタラクティブに質問されすべて答えるとpackage.jsonが生成される。質問をすべてスキップするには-yオプションをつければよい。

# 現在のディレクトリに package.json を生成する
npm init

package.json の中身

package.jsonは例えば以下のようになっている。.jsonという拡張子からもわかるように、このファイルは JSON と呼ばれる形式に従っている。

{
  "name": "my-package",
  "description": "my first package ever",
  "license": "MIT",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "axios": "^0.18.0"
  },
  "devDependencies": {
    "eslint": "^5.14.1"
  }
}

name, version, description, license などのデータは単なるパッケージのメタデータであり、パッケージを公開するつもりがないならばあまり気にする必要はない。機能的に重要なのは main, dependencies, devDependencies, scripts であり、以下でそれぞれ説明する。

main

main はパッケージを外からインポートするときにどの JavaScript ファイルが入り口であるかを指定するものである。誰かのパッケージを外から使うときに、そのパッケージをインポートするとは具体的にどのファイルをインポートするということなのかを確認するときに見ればよい。自分のパッケージのpackage.jsonの main は、そのパッケージを NPM で公開しない限り重要ではない。

例えば、HTTP リクエストライブラリである request というパッケージを下のようにインポートしたいとする。Node.js では外部の JavaScript ファイルをインポートするときrequireまたはimportを使う(後者については近年新しく導入された構文でありここでは触れない)。このとき、このreqという変数には具体的に何が入るのかを調べたいとする。

const req = require('request')

ここで、request をインストールしたあとにnode_modules/request/package.jsonを見ると、下のように main はindex.jsとなっている。

ss-request-packagejson.png

つまり、index.jsがエクスポートしたものが変数reqに入ることになる。index.jsは以下のようになっている。

ss-request-export.png

module.exportsrequestという変数の値が代入されていることがわかる。よって先の変数reqにはこの変数の値が入ることがわかる。

dependencies & devDependencies

先述の通り、dependency とは自分のパッケージが依存する別のパッケージであり、package.jsonには dependency が使用するバージョンとともに書かれる。これらに変更を加える方法は後で説明する。

dependencies と devDependencies の違いであるが、意味としては前者は実行に必要なパッケージ、後者は開発やテストにのみ必要なパッケージである。機能的には、外部のパッケージを dependency としてインストールするときにデフォルトではその dependencies はインストールされるが devDependencies はインストールされないという違いがある。

例えば JavaScript の Linter である eslint は開発のときにのみ必要で実行するときには不要なため、devDep としてインストールするのが適切である。しかしどちらに分類すべきか自明でない場合もある。例えば Webpack を用いてフロントエンド(ブラウザ)アプリを開発するときは、そもそもパッケージとして外から実行できるものではないため分類しがたい。この場合は、Webpack などのビルドツールは devDep、jQuery などのアプリに含まれるべきライブラリは dep として指定するのが普通である。

自分のパッケージを NPM で公開するつもりならば、両者の違いに気をつける必要がある。公開しないとしても、どのパッケージが実行に必要なのかを意識し dep と devDep を使い分けることは、dependency を整理する上でも良い習慣だと思われる。

package.jsonでは次のような形式で dependency が指定される。このときバージョンの先頭にキャレット^またはチルダ~がついていることが多い。

  "dependencies": {
    "express": "^4.17.1",
    "request": "~2.88.0"
  },

そもそもバージョンは.で区切られた 3 つの数字から構成されているが、これはSemver(Semantic Versioning)という規則に則っており(無論、このルールに従うのは開発者の責任である)、下の図のようにそれぞれの数字に意味がある。キャレット^をつけると「Major は一致し Minor と Patch は指定されたもの以上」という意味になり、チルダ~をつけると「Major と Minor は一致し Patch は指定されたもの以上」という意味になる。例えば、^4.17.14.17.104.20.0にマッチするが4.16.83.20.4には一致しない。~2.88.02.88.5にマッチするが2.90.3にはマッチしない。

fig-semver.png

バージョンを固定したいときは後述のpackage-lock.jsonを使えばよいため、わざわざキャレットやチルダを外す必要はない。

scripts

scripts は簡単に言えばコマンドのエイリアスであり、任意のコマンド(i.e. コマンドラインのコマンド)に名前をつけることができる。例えば以下のような形である。

  "scripts": {
    "start": "node index.js",
    "lint": "eslint"
  },

ここに記載された script はnpm run <name>で実行できる。例えば上のlintnpm run lintで実行できる。

ただしいくつかの名前は特別扱いされ、例えばstartは普通プログラムを実行するコマンドを指定し、npm startで実行できる。testはテストを実行するコマンドを普通指定し、npm testで実行できる。また、script 名の先頭にpreがついていると、ついてない名前の script が実行される前に自動的に実行される。例えば scriptbuildprebuildが存在するとき、npm run buildを実行すると、buildの前にprebuildが自動的に実行される。逆にpostを先頭につけると元の名前の script の後に実行される。

preinstallpostinstallはそのパッケージをインストールする前後で自動的に実行されるものであり、何らかのパッケージのインストールが失敗するときはそのパッケージのpreinstallpostinstallをチェックすると解決することもある。

npm run <name>は簡単な task runner2として使えるため、何度も実行するコマンドは script として登録すると開発を効率化できる。また、scripts はプロジェクトのテンプレートに最初から含まれていることが多い。例えば、react のプロジェクトをcreate-react-appで生成するとstartbuildといった script が用意されており、すぐに開発やビルドができるようにセットアップされている。

dependency の編集

すでにnpm installですべての dependency をインストールした状態で、dependency を追加・削除・アップデートしたいときは普通直接package.jsonを編集せず、npm を通じて行う。npm コマンドで dependency を変更すると自動的にpackage.jsonにも反映される。もしpackage.jsonを直接編集した場合は再度npm installを実行してnode_modules内のファイルを更新する必要がある。

Note: npm v4 以下では dependencies として追加するときに--saveを指定しないとpackage.jsonに反映されない。

dependency の追加/バージョン変更

devDep として追加する場合は-D(--save-devのエイリアス)を指定する。

# dependencies に追加
npm install <package>

# devDependencies に追加
npm install -D <package>

バージョンを指定したい場合は@の後に書けば良い。

# react の v16.8.6 を追加
npm install react@16.8.6

# react の最新バージョンを追加
npm install react@latest

dependency の削除

npm uninstall <package>

package-lock.json

これは dependency のバージョンを lock(ロック、固定)するためのファイルであり、npm installの実行時に自動的に作成される。

一般に npm パッケージは他のチームメンバーの開発マシン、テスト環境、本番環境など複数の環境で実行されるため、すべての環境で全く同じバージョンの dependency をインストールしたいと思うのは自然である(バージョンが同じでなければ「ある環境ではうまく実行でき、ある環境ではエラーが出る」といったことが起こりうる)。

実は、package.jsonだけではこれは実現できない。キャレット^やチルダ~を使わなければいいのではないかと思うかもしれないが、問題はそう単純ではない。例えば自分のパッケージの dependency として"A": "1.2.0"と指定したとしよう。パッケージ A はまた別のパッケージ B に依存しており、"B": "^2.5.0"が指定されている。いま、B の最新が v2.8.0 とすると、npm installすれば A の v1.2.0、B の v2.8.0 がインストールされる。しばらくして B の新バージョン v2.9.0 がリリースされたとする。いま、別のマシンで自分のパッケージをnpm installすると A の v1.2.0 と B のv2.9.0がインストールされる。このように、同じpackage.jsonでもインストールされる dependency のバージョンが異なってしまうということが起こりうる。

これを解決するために npm v5 以降でpackage-lock.jsonが導入された。このファイルには dependency、dependency の dependency...と間接的なものも含めすべての dependency のバージョン(とその integrity)が記録される。

npm v6.9.0 時点ではnpm installの具体的な挙動は次のようになっている。

  • package-lock.jsonが存在しないとき
    • package.jsonに基づいて dependency がインストールされ、実際にインストールされたバージョンがpackage-lock.jsonに書かれる。
  • package-lock.jsonが存在するとき
    • package-lock.jsonに基づいてインストールされるが、package.json指定されたバージョンとの矛盾があれば、package.jsonが優先され、実際にインストールされたバージョンがpackage-lock.jsonに書かれる。

package-lock.jsonを優先したい場合はnpm ciを実行すればよい。このコマンドは文字通り CI(Continuous Integration)用に使われることを意図したものであり、package-lock.jsonに基づいて dependency をインストールし、もしpackage.jsonとの矛盾があればエラーを出力する。

GitHub でパッケージのコードを見るときの注意点

NPM パッケージは高確率でソースコードが GitHub でも公開されており、ローカルにインストールすることなくコードを確認したいときに便利である。パッケージのページの Repository の欄をクリックすれば該当の repo に飛ぶことができる。

ss-npm-express.png

先に述べたように、repo にあるコードがそのまま NPM にアップロードされているとは限らない。何らかのビルド処理をしたあと必要なファイルだけアップロードされることもよくあり、package.jsonscriptsを見ればどのようなビルド処理をしたのか推測することもできる。

パッケージの repo はしばしば NPM でのバージョンに対応した tag がつけられており、特定のバージョンのソースコードを見たいときに便利である。

ss-express-tags.png


  1. npm auditで脆弱性のある dependency があるか自動でチェックすることができるが、「報告されている」脆弱性しか考慮されないので安心はできない。 

  2. task runner とは複雑なビルド、テストなどの処理を行いやすくするツールのことであり、Node.js では gulp.js や Grunt などが有名である。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

v8nを使ってvue.jsでバリデーションをかけてみた

はじめまして、普段はproduct managerをやっているtatsukenと申します。
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

何をするのか

v8nというnode moduleを使用してvue.jsの入力フォームにバリデーションをかけていきたいと思います。
vue.jsの一般的なバリデーションライブラリとしてはvee-validateなどがありますが、
v8nは簡単にバリデーションをかけたいときやバリデーションのカスタマイズを行いたいときに便利です。

使い方

install(vue project)

npm install v8n -s 

Sample code

sample.vue
<template>
  <form action>
    <input type="text" v-model="email">
    <input type="password" v-model="password">
    <input type="text" v-model="email">
    <input type="submit" value="decide" @click="onClickSubmitButton">
  </form>
</template>

<script>
import v8n from "v8n";
export default {
  data() {
    return {
      email: null,
      password: null
    };
  },
  methods: {
    onClickSubmitButton() {
      const message = {
        null: "中身が空です",
        minLength: "文字数は4文字以上です",
        pattern: "emailを入力してください",
        maxLength: "文字数が多すぎます"
      };
      try {
        v8n()
          .not.null() // 値がnullじゃないか
          .string() // 文字列
          .minLength(4) // a@b.c を想定して最低5文字
          .pattern(/[^\s@]+@[^\s@]+\.[^\s@]+/)
          .check(this.email);
      } catch (ex) {
        let rule = ex.rule.name;
        alert(`Emailに関して${message[rule]}`);
        return;
      }
      try {
        v8n()
          .not.null()
          .string()
          .minLength(4)
          .check(this.password);
      } catch (error) {
        let rule = error.rule.name;
        alert(`passwordに関して${message[rule]}`);
        return;
      }
    }
  }
};
</script>

説明

  • まずv8nをimportimport v8n from "v8n";
  • v8n()に様々な設定を加えていくことで様々なバリデーションを作ることが出来ます。
code rule
null() nullかどうかを判定する
not.null() null出ないかどうかを判定する
string() stringかどうかを判定する
minLength(4) 4文字以上かどうかを判定する
pattern(/[^\s@]+@[^\s@]+.[^\s@]+/) patternに一致しているかどうかを判定する(今回はemailのpattern maching)
  • 他にも様々なruleをつけることが出来ます(詳しくはこちらにまとまっていました)
  • 加えた条件に対して.check(変数名)を行うことで任意の変数が自分の設定したruleに一致しているか判定することができます
  • あとはpromiseなりtry catchなりでerrorを受け取り、エラーハンドリングを行ってください
  • errorの場合どのruleによってerrorになったかはerror.rule.name;などで受け取ることが出来ます。

参考にしたもの

 最後に

ざっくりとv8nの使い方について説明しました。
初めての投稿なので間違いなどあれば指摘していただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

v8nを使ってVue.jsでバリデーションをかけてみた

はじめまして、普段はproduct managerをやっているtatsukenと申します。
研修の一環でvue.js、expressを書くことがあったので、そのことを中心にまとめていきたいと思います

何をするのか

v8nというnode moduleを使用してvue.jsの入力フォームにバリデーションをかけていきたいと思います。
vue.jsの一般的なバリデーションライブラリとしてはvee-validateなどがありますが、
v8nは簡単にバリデーションをかけたいときやバリデーションのカスタマイズを行いたいときに便利です。

使い方

install(vue project)

npm install v8n -s 

Sample code

sample.vue
<template>
  <form action>
    <input type="text" v-model="email">
    <input type="password" v-model="password">
    <input type="text" v-model="email">
    <input type="submit" value="decide" @click="onClickSubmitButton">
  </form>
</template>

<script>
import v8n from "v8n";
export default {
  data() {
    return {
      email: null,
      password: null
    };
  },
  methods: {
    onClickSubmitButton() {
      const message = {
        null: "中身が空です",
        minLength: "文字数は4文字以上です",
        pattern: "emailを入力してください",
        maxLength: "文字数が多すぎます"
      };
      try {
        v8n()
          .not.null() // 値がnullじゃないか
          .string() // 文字列
          .minLength(4) // a@b.c を想定して最低5文字
          .pattern(/[^\s@]+@[^\s@]+\.[^\s@]+/)
          .check(this.email);
      } catch (ex) {
        let rule = ex.rule.name;
        alert(`Emailに関して${message[rule]}`);
        return;
      }
      try {
        v8n()
          .not.null()
          .string()
          .minLength(4)
          .check(this.password);
      } catch (error) {
        let rule = error.rule.name;
        alert(`passwordに関して${message[rule]}`);
        return;
      }
    }
  }
};
</script>

説明

  • まずv8nをimportimport v8n from "v8n";
  • v8n()に様々な設定を加えていくことで様々なバリデーションを作ることが出来ます。
code rule
null() nullかどうかを判定する
not.null() null出ないかどうかを判定する
string() stringかどうかを判定する
minLength(4) 4文字以上かどうかを判定する
pattern(/[^\s@]+@[^\s@]+.[^\s@]+/) patternに一致しているかどうかを判定する(今回はemailのpattern maching)
  • 他にも様々なruleをつけることが出来ます(詳しくはこちらにまとまっていました)
  • 加えた条件に対して.check(変数名)を行うことで任意の変数が自分の設定したruleに一致しているか判定することができます
  • あとはpromiseなりtry catchなりでerrorを受け取り、エラーハンドリングを行ってください
  • errorの場合どのruleによってerrorになったかはerror.rule.name;などで受け取ることが出来ます。

参考にしたもの

 最後に

ざっくりとv8nの使い方について説明しました。
初めての投稿なので間違いなどあれば指摘していただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サービス終了後もガルラジ公式アプリを使うためのツールを作った

はじめに

ガルラジ公式アプリが2019年6月いっぱいでサービス終了を迎えます.アプリで配信されている「つぶやき」を保存しサービス終了後も閲覧するためのツールを作りました.
https://github.com/sienori/Garuradi-cache-server

ガルラジとは

ガールズラジオデイズ,略してガルラジというコンテンツをご存知でしょうか.2018年12月から翌3月にかけてファーストシーズンが展開された,2次元のキャラクターたちによるラジオコンテンツです.

ラジオという媒体だからこそ醸し出されるキャラクターの実存感や,虚構と現実の境目がグラつくような不思議な感覚に魅了され,これまでも多くのファンによって怪文書が投稿されるなど大きな盛り上がりを見せてきました.

2019年7月からはセカンドシーズンの展開を控えており,今最も熱いコンテンツといえます. #2019年はガルラジ


さて,ガルラジへの没入感を高めるための要素として,ラジオ本編の他にも公式アプリで配信されていたキャラクターたちの「つぶやき」というものがありました.

tweets.png

ラジオ外でのキャラクター達の会話がリアルタイムで更新されていく魅力的なコンテンツでしたが,コンテンツ展開の移動に伴い公式アプリのサービスは2019年7月で終了することが発表されています.

サービス終了後もつぶやきを見返したい!ということで,つぶやきを保存しサービス終了後もアプリで閲覧するためのツールを作りました.

仕組み

Charlesという通信のモニタリングや通信内容の上書きができるソフトがあります.これを使うと特定のアドレス宛の通信を自前のローカルサーバに送ることができます.アプリとAPIサーバの間にローカルサーバを経由させることでリクエストとレスポンスの内容を保存し,オフラインでも利用できるようにします.

通常の通信はこうですが
connection1.png

Charlesでローカルサーバを経由させてリクエストとレスポンスを保存します.
connection2.png

一度保存した通信内容はキャッシュから取り出してアプリに渡すことで,サービスが終了したあとでも閲覧できるようにします.

そんなことして大丈夫なの?と思われるかもしれませんが,通信内容の保存は私的利用の範囲内でローカルにのみ行っており,サーバに対しても通常通りのリクエストしかしていないので問題はないと考えています.

導入

1. Androidエミュレータのインストール

ガルラジ公式アプリを動かすためのAndroidが必要です.
前述のCharlesを使えばスマホ実機の通信をキャプチャすることも出来るのですが,Android7以降の端末ではSSLの通信を覗けなくなりました.
参考: Android7からcharlesでssl通信が見れなくなった件

この記事ではGenymotionというAndroidエミュレータを使用します.Genymotionをインストールし,Android6の端末をインストールしてください.

インストールした端末にガルラジ公式アプリをどうにかしてインストールしてください(Playストアでの配信はすでに終了しています).

2. Charlesのインストール

こちらの記事などを参考にCharlesをインストールし,セットアップします.
【開発支援ツール】Charlesの使い方【神ツール】

ここで重要となるのがSSL証明書の設定です.
上記の記事を参考にPCとAndroidの両方に証明書をインストールしてください.

3. ツールのダウンロード

https://github.com/sienori/Garuradi-cache-server
Githubから任意の場所にクローンしてください.
ツールの実行にはNode.jsが必要です.

git clone https://github.com/sienori/Garuradi-cache-server.git
cd Garuradi-cache-server
npm install

4. 追加の設定

ここまでのセットアップを終えると,AndroidとPC上の通信がCharlesでキャプチャされるようになっているはずです.
charles.PNG
以下の設定を追加で行います.

Recording Settings

関係のない通信を記録しないための設定です.
Proxy>Recording Settingsを開き,IncludeタブのAddボタンから2つのロケーションを追加します.

  • https://app.garuradi.jp recording-g.PNG
  • http://localhost:3000 recording-l.PNG

Map Remote

app.garuradi.jpへの通信をローカルサーバへ流すための設定です.
Tools>Map Remoteを開き,Enable Map Remoteを有効にしてAddボタンからマッピングを追加します.

Map From: https://app.garuradi.jp
Map To: http://localhost:3000
mapremote.PNG

使い方

次のコマンドでツールを起動します.

npm run start

アプリを起動し,つぶやきを読み込んだりするとコマンドプロンプトに次のようなログが表示されるはずです.

/login
Request to server
Save cache /login 0001

/term/timeline
Request to server
Save cache /term/timeline 0001

/term/timeline
Response from cache /term/timeline 0001

これらのログはAPIへのリクエストが行われるたびに行われる処理の内容を表しています.
つぶやきを読み込むとAPIサーバへリクエストが送信され(Request to server),返ってきたレスポンスをキャッシュとしてローカルに保存します(Save cache).
一度保存された内容は,2回目以降はキャッシュから読み出されます(Response from cache).

すべてのつぶやきを読み込んでローカルに保存しておけば,サービスが終了したあともアプリを通してつぶやきを閲覧することができるはずです.

既知の問題

読み込みを繰り返していると,通信エラーと表示されることがあります.
error.PNG
多くの場合は何度かリトライすると読み込めるようになりますが,ログに302 'https://app.garuradi.jp/login/error'と表示されている場合はアプリの再起動が必要です.

まとめ

ガルラジ公式アプリの「つぶやき」を保存するツールを作りました.サービス終了までに保存しておけば,いつでもつぶやきを見返すことができるはずです.

https://github.com/sienori/Garuradi-cache-server
ご利用の際は自己責任でお願いします.

#2019年はガルラジ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sequelizeでモデルからENUMの値を取得する方法 [Node.js + Express]

はじめに

Node.js + Expressを使って開発を行った際に、sequelizeを使ってモデル内のカラムからENUMの値を取得した時のやり方をメモしておきます。

モデルの定義確認

以下のように簡易モデルを定義したとします。

sequelize.define('model', {
  animals: {
    type:   Sequelize.ENUM,
    values: ['dog', 'cat', 'bird']
  }
})

取得方法

Model.rawAttributes.states.valuesでENUMの値を取得できます。

var Model = sequelize.define('model', {
  states: {
    type:   Sequelize.ENUM,
    values: ['dog', 'cat', 'bird']
  }
});

console.log(Model.rawAttributes.states.values);
// logs ['dog', 'cat', 'bird'] in console

カラムからENUMの値を取得する場合は、model.column.rawAttributes.states.valuesなどのようにすると同様に取得可能です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む