(node.js, express) APIでログインするときにセッションが消えてうまくログインできないことがあった

node.js + expressで開発をしたときに、APIでログインするときにセッションが消えてうまくログインできないことがあった。

まず、以下のようなことをやってみた。

  • req.jsonなどで返す前に明示的にsession.save(() => ...)でセッションを保存
  • クライアントでのfetchのオプションにcredentials: 'include'を指定
  • サーバで返すときにres.header('Access-Control-Allow-Credentials','true');とヘッダーを指定

これは関係なかったようで、解決せず、結局以下のことが原因だった。

  • ログインのAPIをGETで叩いていた。(別のAPIを転用したため)
  • 別のGET APIで短期間で2回アクセスしたら304 not modifiedが返っていたため、ログインの結果が反映されていなかった。毎回Last-Modifiedヘッダーを指定することで解決。
  • APIを同時に複数アクセスしていたため、セッションクッキーの更新が競合していた(言われてみれば当然だが...)。最初にログインAPIだけ投げるようにした。
    • それでも解決しないと思ったら、favicon取得のアクセスが同時に行われていたのが原因のようだった。(今までfaviconを置いていなかった)serve-faviconを使ってちゃんとfaviconを指定したら解決した。

ChromeのDeveloper Toolsのnetworkで、リクエストのタイミングとか載ってきているcookieとかを見るとこういうことをデバッグできることを知った。

Sequelizeやexpressでハマったこととか

わざわざ1つの記事にするまでもなさそうなことをメモする。

Sequelize.jsの話

belongsToとhasManyの違い

sql - belongsTo vs hasMany in Sequelize.js - Stack Overflow

公式ページにも書いてあった気がする。

例えば、Album.belongsTo(Artist)と書くとalbum.getArtist()のようにアクセスできる。 逆に、Artist.hasMany(Album)と書くとartist.getAlbums()のようにアクセスできる。

つまり、1つを書いただけだと、片方向のアクセスしかできない。

PostgreSQLでテーブル名やカラム名をダブルクオーテーションで囲わないと見つからないというエラーになる

Sequelizeはテーブル名やカラム名をcamel caseで作るが、PostgreSQLSQLを直に書くときはこれらの識別子をダブルクオーテーションで囲わないと全て小文字に自動的に変換されてしまう。 SQLを直で書くときは、すべてダブルクオーテーションで囲んで書けば解決。

many to manyのthrough tableが生成されない

各モデルごとにUser.sync()とだけやっていたのが原因。

sequelize.sync()を実行しないといけない。

Sequelizeでサブクエリの結果が入るはずのカラムがモデルのインスタンスの変数から取得できない

要するに、普通はuser.userNameのように取得できるはずが、もともとモデルに無いカラムでは取得できないということ。

user.getValue('追加したカラム名')としたらいけた。

TypeScriptでモデルを定義したらTypeError: Class constructor Model cannot be invoked without 'new

Class constructor Model cannot be invoked without 'new' and how getter v4 work? · Issue #7840 · sequelize/sequelize · GitHub

ビルド時にクラスが関数に変換されるために発生するエラー。

@babel/preset-envtargets"node": "current"になっているのに発生した。

結局、tsconfig.jsonでのコンパイルターゲットがes5になっていたのが原因だった。es6にしたら治った。

{
  "compilerOptions": {
    "target": "es6",
   ...

node.jsやexpressなど

Cannot find module '@babel/core', Cannot find module 'babel-preset-env'

古い情報と新しい情報をごっちゃにしてやっているとどこかでこのエラーが起きた。

新しいのは@babel/core@babel/preset-envなので、そっちをちゃんとインストールして設定する。

Error: ENOSPC: System limit for number of file watchers reached, watch ' ... '

Error: ENOSPC: System limit for number of file watchers reached, watch '/home/foldername/abcrypto/static' · Issue #11406 · gatsbyjs/gatsby · GitHub

ファイルシステムのfile watcherの上限に達したということらしい。

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -pで治った。

Jestでのテストでopen handle potentially keeping Jest from exitingというエラー

まず、expressサーバはテストのときはlistenしてはいけない

javascript - Jest detects open handle with Express app - Stack Overflow

なので、例えばまず/app.jsに共通の処理を書き、

const express = require('express');
const app = express();

app.use('/api', ...

module.exports = app

本番で起動するserver.jsでは以下のようにapp.jsを読み込む。

const app = require('./app.js')
app.listen(3000, () => console.log(`Listening on port: ${PORT}`))

そして、テストを実行するtest.jsではapp.jsを読み込む。

const app = require('./app.js')

describe('test block', () => { ...

もう1点、テストのときは最後にsequelizeのデータベースをcloseする必要がある

// 他のモジュールで, ``export const database = new Sequelize( ...``としたやつ
afterAll(async () => {
  await database.close();
})

Sequelize | Sequelize

それ以外にもasyncでテストを書くとうまく動かないみたいな報告があるが、自分の場合には該当しないようだった。

Asynchronous beforeEach / beforeAll? · Issue #1256 · facebook/jest · GitHub

(ポエム) SequelizeなどORMつらい

ちょっと複雑なクエリを書こうとすると、書き方を調べないといけないし、生成されるSQLもわかりにくくなって辛くなってくる。

結局、ORMのメリットは単純なクエリのオブジェクトへのマッピングが簡単にできることであって、やっぱり複雑なクエリを書いたりパフォーマンスを最適化したりするのには向いていないという話を聞いて納得した。

Code rant: When Should I Use An ORM?

TypeScriptでreact-bootstrapのForm.ControlのonChangeイベントの型を付ける

Form.ControlのonChangeイベントだけじゃないかも。

React-bootstrap + TypeScriptにおいて、下記のようにForm.ControlのイベントハンドラhandleChangeの型をどうつければ良いのか分からなかった。

<Form.Control type="text" onChange={handleChange} />

vscode型推論されるのは((event: React.ChangeEvent<FormControlElement>) => void)という型のようだが、その型を書いてもFormControlElementというメンバはreact-bootstrapには無いというエラーになる。

ググったら出てきた。

@types/react-bootstrap form event typings are unusable · Issue #16208 · DefinitelyTyped/DefinitelyTyped · GitHub

結局、(e: React.ChangeEvent<typeof FormControl & HTMLInputElement>) => voidという型をhandleChangeにつけたらうまくいった。理由はよくわかっていない。

Sequelizeでwebpackのproduction時にテーブル名が変わって直書きSQLでエラーになる対策

sequelizeというnode.jsのORMを使う際に、webpackのproductionモードでビルドを行うと、コードがminifyされて、モデルのクラス名が変更されてしまい、literalで直書きのSQLでエラーが発生するということがありました。

あまり一般的な事象ではないようなので、TypeScript固有の問題かもしれません。

下記リンク先の記事で同じ問題に対処しています。

試行錯誤な日々: webpackのproductionモードでも動くSequelizeの直書きSQLを書く方法

しかし、私の場合、記事で紹介されている解決策である「literalで直書きしてあるSQL中のテーブル名を${User.name}のように取得する」というやり方では、多対多結合におけるthroughテーブル名の指定の仕方が分からず、うまくいきませんでした。

結果的にはモデルの定義のときにtableNamemodelNameを両方指定することで解決しました。

例:

User.init(
  {
    id: {
      type: DataTypes.INTEGER.UNSIGNED,
      autoIncrement: true,
      primaryKey: true,
    },
    name: {
      type: new DataTypes.STRING(128),
      allowNull: false,
    },
  },
  {
    tableName: "Users",
    modelName: "User",
    sequelize,
  }
);

情報源

実は、このことは以下のページの「Minification」の項目に書いてありました。

GitHub - RobinBuschmann/sequelize-typescript: Decorators and some other features for sequelize

最初にコードを書く際には下記のsequelizeの公式ページを参照しましたが、こちらにはtableNameしか指定していなかったため、見事にハマりました。

Manual | Sequelize

なお、tableNameはDBに実際に作られるテーブルの名称、modelNameはsequelizeにおけるモデルの名称という違いがあります。

デフォルトではtableNameは複数形 (e.g. Users)、modelNameは単数形 (e.g. User)です。

ツイート数でランキング表示するRSSリーダ

記事をTwitter検索して、ツイート数とコメントを表示するRSSリーダを作りました。

f:id:kamocyc:20200802154400g:plain

ancient-shelf-27599.herokuapp.com

例えば、https://b.hatena.ne.jp/hotentry/all.rssや、https://rss.itmedia.co.jp/rss/2.0/netlab.xmlを登録してみるとたくさん出てきます。

主な機能

  • RSS中の記事のURLをTwitterで検索し、URLが含まれるツイート数を取得
  • ツイート数と記事の公開時間を考慮してソート
  • Twitterからコメントを取得して一覧表示
  • はてなブックマークからも同様に件数とコメントを取得して表示

まだ直したいところはありますが、いったん一区切りにしたいと思います。

注意点

  • Twitter APIの制限によく引っかかります。15分経つと解消されます。
  • Herokuの無料サーバを使っているのでかなり重いです。おそらく同時アクセスは2~3人が限度です。

ソース

  • バックエンド: node.js, express
  • フロントエンド: React

github.com

ツイーヨ変換 ブックマークレット

Twitterにおける「ツイート」→「ツイーヨ」のような変換規則が気になったので、変換するブックマークレットを作ってみました。

f:id:kamocyc:20200801110232g:plain

github.com

下記をブックマークのURLに登録すれば動きます。

javascript:(function(){var d=document;d=d.selection?d.selection.createRange().text:d.getSelection().toString();console.log(d);prompt("Result:",function(e){function g(a){return a.match(/^[\u30a1-\u30f6\u30fc]+$/)?!0:!1}var h=[{from:"\u30d5\u30a9",to:"\u30d2\u30e7",tail:!1},{from:"\u30e9",to:"\u30e4",tail:!1},{from:"\u30eb",to:"\u30e6",tail:!1},{from:"\u30c8",to:"\u30e8",tail:!1},{from:"\u30c9",to:"\u30e8",tail:!1},{from:"\u30ed",to:"\u30e8",tail:!1}],k=h.map(function(a){return a.from}).join("|"),l=new Map(h.map(function(a){return[a.from,a]}));return e.replace(new RegExp("("+k+")","g"),function(a,f,b){f=l.get(a);b+=a.length;var c;if(c=f.tail){--b;c=e.length;if(b>=c)throw Error("Illegal index");c=!(b===c-1||b===c-2&&"\u30fc"===e[c-1]||!g(e[b+1])||"\u30fc"===e[b+1]&&!g(e[b+2]))}return c?a:f.to})}(d))})();

「アンドロイド」 → 「アンヨヨイヨ」、「フォロワー」 → 「ヒョヨワー」などと変換されます。(ラ、ル、ロは変換しすぎかも)

参考にしたのは下記まとめです。 7年前にすでに考えられていたので、たぶん当プログラムと同じようなことをしている人は既にたくさんいると思います。

togetter.com

役立ったページなど

Stack Overflow中毒

Stack Overflowという英語圏のプログラミング関連の質問サイトがあります。

で、私は元々トラブルシューティングなどで閲覧していたのですが、たびたび質問や回答に対してコメントしたいのにできないという歯がゆさを感じていました。

というのは、Stack Overflowではreputationというものを一定以上獲得しないとコメントができないのです。reputationは、回答や質問が良いものであると他のユーザに評価されるとたまります。

そこで、reputationを貯めることと英語のライティングの練習になるかなと思い、回答してみることにしました。

活動に当たっては、以下のページを参考にしました。

特に質問することも無いので、一応得意分野であるVBA関連の質問を解答することにしました*1

いくつかベストアンサーをもらって*2、無事にコメントができる50 reputationを超えました。

f:id:kamocyc:20200720120837p:plain

それでもついサイトを見て、質問を見て回答してしまって、中毒性高いなと感じました。

実際に、Stack Overflowでも、中毒になっているからどうすればいい?という質問があったりします。(もちろん、それは他の質問サイトでも同じです。質問サイトって中毒になりませんか。 - アンケート 締切済み| 【OKWAVE】

meta.stackexchange.com

ギャンブル依存症との同様の治療をすれば?という意見や、子供ができると治るという意見、ペアレンタルコントロールツールを使うなどの意見があります。

中毒になる理由としては、reputationというゲーミフィケーションもさることながら、他の人から感謝されて認められるのも大きいです。

コメント書くので英語の勉強になるという言い訳も効きますし*3

とはいえ、あまり1日何時間とかもやってしまうと生活に支障をきたすので、1日あたりの時間を制限しようと思います。

*1:PHPPython、Computer Scienceの質問も見てみたけど、答えられそうなものが無い...

*2:やたら面倒な質問に当たったり、「できません!」と回答したらつよつよマンが現れて「こうしたらできるよ!」と回答されたりもしました。VBA関連でつよいっていうのは、Windows APIやCOMを使いこなしてVSTOVB.NETにも詳しいみたいなそんな感じです。Microsoft MVPってすごい。

*3:ほぼDeepLのコピペだったりします。DeepLは本当に神サービスです。これが無いと英文書けません...