20190301のNode.jsに関する記事は6件です。

macOS+Node.js�+OpenCV環境の構築

前提

OpenCVで遊ぶならPython環境が鉄板だと思います。

本稿はそれでもNode.jsからOpenCVを使いたい人向けの記事です。

Node.js+OpenCVの選択肢

Node.jsからOpenCVを使いたい場合、ライブラリとして主な選択肢は次の2つになります。

前者はOpenCV version 2、後者はOpenCV version 3のライブラリになります。

前者は全然メンテされていません。最新バージョンの6.0.0リリースから2年経っています。後者はそこそこメンテされていそうです。

OpenCV3の方が画像まわりのAPIが増えているらしいので、顔認識をしたい場合は後者を使った方が良い可能性があります。

前者の方がネット上の利用例は多そうですが、Pythonに比べたら利用例は圧倒的に少ないので、その意味では五十歩百歩かもしれません。

macOSで使おう

さて、上記ライブラリのうち、前者のライブラリ opencv をmacOS上で使おうと思うとイマイチうまくいきません。ネット上で調べてみても失敗事例が多い気がします。

後者のライブラリ opencv4nodejs は次のようにすれば使えます。

$ brew install cmake opencv@3
$ brew link --force opencv@3
$ export OPENCV4NODEJS_DISABLE_AUTOBUILD=1
$ npm i opencv4nodejs

brew link するのはカッコ悪いんですがlinkしないと正しく動くバイナリが作れませんでした。

動作確認

ビルドがうまくいっているか、動作確認してみましょう。

$ curl http://www.lenna.org/len_std.jpg -o lenna.jpg

で有名なLennaさんの画像を用意した上で

lenna.js
const cv = require('opencv4nodejs');

const img = cv.imread('./lenna.jpg');

const classifier = new cv.CascadeClassifier(cv.HAAR_FRONTALFACE_ALT2);

const grayImg = img.bgrToGray();

const result = classifier.detectMultiScale(grayImg);

if (!result.objects.length) {
  throw new Error('failed to detect faces');
}

const minDetections = 10;
result.objects.forEach((faceRect, i) => {
  if (result.numDetections[i] < minDetections) {
    return;
  }
  const rect = cv.drawDetection(
    img,
    faceRect,
    { color: new cv.Vec(255, 0, 0), segmentFraction: 4 }
  );
});
cv.imshowWait('result', img);

上記スクリプトを実行すると次のウインドウが現れます。

image.png

成功です。やりましたね。

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

2019年の今 Mithril.js を使うためのTips

最近めっきり話を聞かなくなったJSフレームワーク、 Mithril.js に関する Tips をつらつらと書いていきます。
決して下火になったわけじゃあないんです。

Mithril.js とは

https://mithril.js.org/

いわゆる仮想DOMフレームワークの一つ。メジャーな React や Angular と比べ、軽量・シンプル・高速なことを謳う。

フレームワークの機能としては、React, Vue と比べても決して不足していないうえ、実際のプロダクトにも採用例があり十分ポテンシャルはある。はずです。

前述の通りバズってはいないですが、現在も活発に開発が続けられており、Ver.2のリリースも予定されています

Mithril 情報を集めるために

重要な点として、Mithril は v0.2 系と v1.x 系で大きくAPIが異なっています。 v1.xの方が正式となり、v2でも大きな変更は入らないようなので、この点だけは覚えておいてください。

早い話が、 Controller あるいは m.props が話題に出る記事は 古いです。これからは参考にしてはいけません。

じゃあどうなったかと言うと、現在はよりシンプルかつ賢くなって、m.props を使わずとも変更が検知されるし、controller を使わずともライフサイクルに応じたフック処理を実行できます。より簡単かつ高速になった純粋な上位互換と言えるものなので、この点でも今から v0.2 系の知識を仕入れる意味はありません。

ここまで分かればあとは公式サイトなり、新し目の記事なりを漁って導入は出来るかと思うので、そのフェーズは他の記事に譲ります 手抜きともいう

以下は使っていく上で気づいたポイントを書き連ねたものです。

vdom.state は使わない

基本的に view に state をもたせるのは99%アンチパターンです。状態に類するものは model のオブジェクトとして分離し、 view 側ではそれを読みに行くだけにしましょう。

例: テーブルの各列コンポーネントに状態をもたせるのではなく、元データにその状態をもたせて、コンポーネントは filter をかけるなどして出し分ける。

arrow function 最強説

arrow function はどんどん活用しましょう。

// 普通に書いてみる
const Component = {
  view: function(vdom) {
    // ...
  }
}

// arrow function で書いてみる
const Component = {
  view: vdom => {
    // ...
  }
}

// 値を返すだけのfunctionはもっと短くできる
const Component = {
  view: v => m("button", { onclick: model.incCount }, "+1"),
}

これに加えて map filter reduce などのメソッドや、三項演算子を組み合わせることでよりコンポーネントをスマートに書けます。楽しくかっこよくコーディングしましょう。

form の取扱い

結論から言うと、 formは使わずformっぽいスタイルを使って実現します 。 (bootstrap なら .form-group が使えます)

const FormLikeComponent = {
  view: v => [
    m(".form-group", [
      m("input[type=text]", { value: model.textData, oninput: e => model.textData = e.target.value }),
      m("button", { onclick: model.submit }, "送信"),
    ]),
  ],
}

let model = {
  submit: e => {
    m.request({
      url: someUrl,
      data: {text: model.textData},
    })
    .then( /* some handling */ );
  },
}

form が使えないとはいえ十分シンプルに書けます。ポイントは、

  • oninput イベントなどを用いて入力値を補足します。入力値を得るには、イベントオブジェクトの target.value を参照します。ここで得た値は例によって state ではなく model のどこかに持たせるべきです。
  • form 内の button なりなんなりで、submit 相当のイベントを発火します。そこでは Mithril らしく m.request を使います。要は普通のリクエストですね。フォームだからと特別な処理は必要ありません。

input 要素を見てもらえれば分かる通り、こんな感じで双方向バインディングな処理が簡単に書けるのが Mithril の素敵ポイントですね。formっぽいスタイルがないときは頑張ってください。

request まわり

request について書きたかったけど力尽きたので一旦ここまでで。

TypeScript まわり

TypeScript について (ry

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

Nest.jsは素晴らしい

Node.jsとExpressでサーバー側のシステムを開発していたのですが、Nest.jsと言うサーバー側のフルスタックフレームワークがあることを知り、乗り換えることにしました。

メリットとデメリット

どんなものにも、メリットとデメリットがあります。自分にとって「メリット>デメリット」となったら採用する価値が出てきます。
私が感じたNest.jsのメリットとデメリットは以下の通りです。

メリット

  • Angular風なので、クライアントにAngularを採用するのであれば、同じような考え方で開発することができる
  • TypeScriptで開発しやすい
  • 予め以下のような構成でシステムを作ることができ、メンテナンス性が向上する(人による実装方法の違いをある程度抑制できる)
    • controller
    • filter
    • guard
    • interceptor
    • interface
    • middleware
    • module
    • pipe
    • provider
    • resolver
    • service
  • テストが容易なコードが書ける
  • nest cliでコードのひな形を作成できる(テストコードも作成できる)
  • Passport、GraphQL、TypeORMなどをサポートしている
  • ドキュメントが充実している(但し、英語)

デメリット

  • 日本語の情報が少ない

NestJSのインストール

NestJSはnpmパッケージとして提供されています。インストールは以下のようにします。

npm install @nestjs/cli -g

まずやってみる

nest cliでプロジェクトを作成します。
以下のようにコマンドを起動するといくつかの質問があり、それに答えていくとプロジェクトが作成できます。

nest new プロジェクト名

sc.png

どうでしょう?Angularっぽいでしょ?

フォルダ構成を決める

これから作成するシステムのフォルダ構成を決める必要があります。
よくあるパターンは、エンティティ(モデル)、サービス、コントローラ単位でフォルダを分けます。なんとなく、それがシンプルでいいのかと思ったのですが、nest cliでソースコードを作ると、そういうフォルダ構成にできそうにありません。
例えば、Userサービスを作成するために、以下のコマンドを実行します。

nest g service User

すると、srcフォルダにUserと言うフォルダができ、その中にコードが生成されます。
では、フォルダ指定すればいいのかと思い、以下のようにコマンドを実行します。

nest g services/User

今度は、srcフォルダにservicesというフォルダができ、さらにUserというフォルダができて、その中にコードが生成されます。

結論
「NestJSは、オブジェクト単位でフォルダを構成する」

では、Userに関するコードを作って見ましょう

必要なコードの種類は、module、entity、service、controller、interfaceです。
REST APIではなく、GraphQLを採用する場合は、controllerは必要ありません。

Userインターフェースの作成

以下のコマンドで、Userインターフェースのひな形を作成します。

nest g interface User/User

Userフォルダにuser.interface.tsができますので、必要なユーザ属性を追加します。

user.interface.ts
export interface IUser {
    id: string;
    name: string;
    kana: string;
    email: string;
    postcode: string;
    address: string;
    phone: string;
    password: string;
    admin: boolean;
    createdAt: Date;
    updatedAt: Date;
}

Userエンティティの作成

以下のコマンドで、Userエンティティのひな形を作成します。

nest g class User/User

Userフォルダにuser.entity.tsができますので、必要なメンバーを追加します。
下記コードは、ORMにTypeORMを使っていますので、そのためのデコレーターが付いています。

user.entity.ts
import 'reflect-metadata';

import { Entity, Column, PrimaryGeneratedColumn, PrimaryColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { IUser } from './user.interface';

@Entity()
export class User implements IUser {
    @PrimaryGeneratedColumn('uuid')
    id: string;

    @Column()
    name: string;

    @Column()
    kana: string;

    @PrimaryColumn()
    email: string;

    @Column()
    postcode: string;

    @Column()
    address: string;

    @Column()
    phone: string;

    @Column()
    password: string;

    @Column()
    admin: boolean;

    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;
}

Userサービスの作成

Userオブジェクトに対して様々な操作を行うサービスクラスを作成します。
以下のコマンドでUserサービスのひな形が作成できます。

nest g service User

今回は、Eメールアドレス指定でユーザ情報を検索する(findByEMail)と、ユーザを追加する(add)を実装します。

user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';

import { User } from './user.entity';

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private readonly userRepository: Repository<User>
    ) {}

    // 指定されたEメールアドレスのユーザを検索する
    findByEmail(_email: string): Promise<User> {
        return new Promise((resolve, reject) => {
            this.userRepository.findOne({ email: _email })
                .then((user: User) => {
                    resolve(user);
                });
        });
    }

    // ユーザを追加する
    add(user: User): Promise<User> {
        this.findByEmail(user.email)
        .then((currentUser: User) => {
            if (currentUser !== undefined) {
                return currentUser;
            }
            else {
                return new Promise((resolve, reject) => {
                    if (currentUser === undefined) {
                        // パスワードをハッシュ化する
                        user.password = this.getPasswordHash(user.password);
                        // ユーザ情報を設定する
                        this.userRepository.save<User>(user)
                        .then((result: User) => {
                            resolve(result);
                        })
                        .catch((err: any) => {
                            reject(err);
                        });
                    }
                });
            }
        })
        .catch((err: any) => {
             throw err;
        });
        return undefined;
    }

    // パスワードをハッシュ化する
    private getPasswordHash(_password: String) {
        const saltRounds: number = 10;
        const salt: string = bcrypt.genSaltSync(saltRounds);
        return bcrypt.hashSync(_password, salt);
    }
}

Userモジュールを作成

最後にUserの構成を設定するUserモジュールの作成です。
以下のコマンドで、Userモジュールのひな形を作成できます。

nest g module User

user.module.tsを以下のように実装します。
このコードもORMにTypeORMを使っていますので、それらしい実装が入っています。

user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { UserService } from './user.service';
import { User } from './user.entity';

@Module({
    imports: [ TypeOrmModule.forFeature([User]) ],
    providers: [ UserService ],
    controllers: [  ],
    exports: [ UserService ]
})
export class UserModule {}

Userモジュールの登録

最後に、srcフォルダにあるapp.module.tsに、UserModuleとUserServiceを登録します。

app.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user/user.service';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    UserModule
  ],

  providers: [
    UserService
  ],

  controllers: [
  ]
})
export class AppModule {}
}

ざっと、これで実装完了です。

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

VisualStudio CodeでNode.jsを実行するときに.envに定義したものをロードする

Node.jsでアプリを作るときに、プログラム上には埋め込みたくない情報を、.envというファイルを作って管理する事が多いです。
例えば、以下のようなものがあるでしょうか。

.env
# Postgresql ENV
DATABASE_URL=postgres://postgres:password@localhost:5432/db

# Session Secret
SESSION_SECRET=KNLCKSO+JPOSJPOS

# NODE Environment
NODE_ENV=development

# JWT Secret
JWT_SECRET=@ofpowegldmvpvnp

今までのやり方

今までは、dotenvパッケージをインストールし、プログラム上に以下のように記述し、.envの内容をロードしていました。

sample.ts
import * as dotenv from 'dotenv';
dotenv.config();

これでもいいのですが、いちいちこう記述するのもめんどくさい。
Herokuにデプロイする事を考えると、そもそも必要ないことだし(Herokuでは、コンソールから環境変数を予め定義しますので、.envファイルそのものが必要ありません)

これは便利

何か他にいい方法はないものかと模索していましたら見つけました。
VisualStudo Codeに.envをロードする機能があるのです。
.vscodeフォルダにlaunch.jsonというプログラムの実行を定義するファイルがあります。
その定義の中に、envFileを追加し、.envのファイル名を指定すると、実行するときに.envの内容をロードしてくれます。

launch.json
{
    "version": "0.2.0",
    "configurations": [
        // nodeによるnode.jsのデバック設定
        {
            "type": "node",
            "request": "launch",
            "name": "node",
            "program": "${workspaceFolder}\\dist\\server\\server.js",
            "envFile": "${workspaceFolder}/.env", // ← ココ!!
            "sourceMaps": true
        }
    ],
}

これでコードを書くことなく、.envに定義したものを、process.env.XXXXXとして使えるようになります。
便利!!

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

SlackとCloud Functionsで迅速に障害の初動対応をする

突然障害が起きると何をしたらいいか慌ててしまいませんか?
今回それを解決するために、1アクションで障害の初動対応をするslash commandを作りました。

概要

障害対応の初動を早めるために

  1. Slackで/hogeと打つ
  2. チャンネルが作られる
  3. チームメンバーがinviteされる
  4. タスクリストがpostされる

という仕組みを用意しました。
usage.gif
上の例は誰もいないスペースでテストしています。
スペースにメンバーがいるとき、Slack Appのレスポンスはこのようにチャンネル名と、招待したメンバーが出力されます。
image.png

環境

  • serverless 1.37.1
  • Node.js 8
  • Google Cloud Functions
  • Slack

背景

障害対応のとき

  • 突然のアラートに何をすればいいかわからない
  • チームの誰が何の対応をしているかわからない
  • ベテラン1人しか対応できないので時間がかかる
  • チーム外から作業状況が見えない
  • 社内Wikiで状況をまとめているがリアルタイム性が低い

というようなことがあると思います。

コンセプト

アラート検知後、以下のようなことを目指します。

  • 障害の初動を迅速に行う
  • チームメンバーの習熟度に関係なく全員で障害に当たることができる
    タスクリストを表示することで突然の障害にも最低限何をすればいいかわかる
  • 作業状況をオープンにする
    パブリックチャンネルを作ることで見たい人が情報を参照できる
    slackのリアクションで何をやっているかわかるようにする
  • 作業状況がリアルタイムにわかるようにする

あくまで初動のための仕組みです。アラートの種類によって調査手段は変わります。
タスクリストはチームごとに適宜更新していく想定です。

仕組み

SlackとGoogle Cloud Functionsを使って概要にある仕組みを構築しました。
フレームワークはServerlessを使いました。
image.png

  1. slash commandsでCloud FunctionsのHTTPトリガーを叩きます。
  2. slash commandは3秒のタイムアウトがあるのでまずはレスポンスを返します。
  3. 非同期でSlack APIを叩きます。
  4. Slack APIの結果を取得します。
  5. API呼び出しの結果をresponse_urlにPOSTします。

チャンネル名のprefix、招待するユーザ、タスクリストはyaml形式の設定ファイルに書きます。
チャンネル名は設定ファイルのprefixと時刻のsuffxが合わせて作られます。
(障害対応を想定して作ったので、チャンネル名を一覧したときわかるように時刻を入れてみました。)

ソース

https://github.com/tom-256/emergency-slack

まとめ

メンバーの習熟度にかかわらず、チーム全体で透明性のある障害対応をおこないましょう。

今回の挙げた例はあくまで一例です。
チームに合った障害対応策を考えてみてはいかがでしょうか?

参考

https://about.gitlab.com/2017/02/10/postmortem-of-database-outage-of-january-31/
GitLabのデータベースの事件は記憶に新しいでしょう。
対応状況をYouTube配信やGoogleDocsで外部に公開するのは難しいかもしれませんが、
関係部署など適切な範囲でオープンにできるといいですね。

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

Node.jsを使って、特定のツイートをしている人をフォローする

お馴染みのtwitterモジュールを使います。

フォローのAPIの公式ドキュメントはこちら(POST friendships/create)

使うメソッド

  • .stream('statuses/filter')で特定ツイートを検索
  • .post('friendships/create')でフォローします。

フォローする際はユーザーのIDかスクリーンネームを渡してあげる模様です。

フォロー関連はなかなかサンプルが見つからないですね。

https://www.npmjs.com/package/twitter

筋トレのハッシュタグでツイートしている人をフォローするコード

app.js
'use strict';

const twitter = require('twitter');
const client = new twitter({
    consumer_key: '', // consumer keyを記入
    consumer_secret     : '', // consumer secretを記入
    access_token_key    : '', // access tokenを記入
    access_token_secret : '' // access token secretを記入
});

// async/awaitで表現
const main = async () => {
    const stream = await client.stream('statuses/filter', {'track':'#筋トレ'});
    stream.on('data', async data => {
        console.log(data.id_str);
        try {
            await client.post('friendships/create', {user_id:data.user.id});
            console.log(`${data.user.name}さんをフォローしました。`);            
        } catch (error) {
            console.log(error);
        }

    });
}

main();

最初ミスったエラー

こんな感じでCannot find specified user.というエラーメッセージがちょくちょく出てました。

[ { code: 108, message: 'Cannot find specified user.' } ]

こちらの記事を見て気づきましたが、client.post('friendships/create', {user_id:data.id});という形で ユーザーのIDじゃなくてツイートのIDを指定してしまっていたのが原因だったみたいです。

tweepyでツイート検索キーワードでフォローしたいがエラーで出る

正しいのは、client.post('friendships/create', {user_id:data.user.id});という指定ですね。

参考

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