(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だけ投げるようにした。
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で作るが、PostgreSQLのSQLを直に書くときはこれらの識別子をダブルクオーテーションで囲わないと全て小文字に自動的に変換されてしまう。 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
ビルド時にクラスが関数に変換されるために発生するエラー。
@babel/preset-env
のtargets
は"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 ' ... '
ファイルシステムの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(); })
それ以外にもasyncでテストを書くとうまく動かないみたいな報告があるが、自分の場合には該当しないようだった。
Asynchronous beforeEach / beforeAll? · Issue #1256 · facebook/jest · GitHub
(ポエム) SequelizeなどORMつらい
ちょっと複雑なクエリを書こうとすると、書き方を調べないといけないし、生成されるSQLもわかりにくくなって辛くなってくる。
結局、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
には無いというエラーになる。
ググったら出てきた。
結局、(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テーブル名の指定の仕方が分からず、うまくいきませんでした。
結果的にはモデルの定義のときにtableName
とmodelName
を両方指定することで解決しました。
例:
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
しか指定していなかったため、見事にハマりました。
なお、tableName
はDBに実際に作られるテーブルの名称、modelName
はsequelizeにおけるモデルの名称という違いがあります。
デフォルトではtableName
は複数形 (e.g. Users
)、modelName
は単数形 (e.g. User
)です。
ツイート数でランキング表示するRSSリーダ
記事をTwitter検索して、ツイート数とコメントを表示するRSSリーダを作りました。
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からコメントを取得して一覧表示
- はてなブックマークからも同様に件数とコメントを取得して表示
まだ直したいところはありますが、いったん一区切りにしたいと思います。
注意点
ソース
- バックエンド: node.js, express
- フロントエンド: React
ツイーヨ変換 ブックマークレット
Twitterにおける「ツイート」→「ツイーヨ」のような変換規則が気になったので、変換するブックマークレットを作ってみました。
下記をブックマークの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年前にすでに考えられていたので、たぶん当プログラムと同じようなことをしている人は既にたくさんいると思います。
役立ったページなど
- ブックマークレットのつくり方
- Ubuntuでgitで画面キャプチャ
- Ubuntu 画面をキャプチャしてgifアニメを作成する(Peek) - Symfoware
- peekというソフトを使って簡単にできました。フレームレートは5pfsくらいで良さそうです。
- Ubuntu 画面をキャプチャしてgifアニメを作成する(Peek) - Symfoware
Stack Overflow中毒
Stack Overflowという英語圏のプログラミング関連の質問サイトがあります。
で、私は元々トラブルシューティングなどで閲覧していたのですが、たびたび質問や回答に対してコメントしたいのにできないという歯がゆさを感じていました。
というのは、Stack Overflowではreputationというものを一定以上獲得しないとコメントができないのです。reputationは、回答や質問が良いものであると他のユーザに評価されるとたまります。
そこで、reputationを貯めることと英語のライティングの練習になるかなと思い、回答してみることにしました。
活動に当たっては、以下のページを参考にしました。
- 使い方: Stack Overflowの使い方 - Qiita
- 活動の仕方: Stack Overflow活動 がんばるぞい
特に質問することも無いので、一応得意分野であるVBA関連の質問を解答することにしました*1。
いくつかベストアンサーをもらって*2、無事にコメントができる50 reputationを超えました。
それでもついサイトを見て、質問を見て回答してしまって、中毒性高いなと感じました。
実際に、Stack Overflowでも、中毒になっているからどうすればいい?という質問があったりします。(もちろん、それは他の質問サイトでも同じです。質問サイトって中毒になりませんか。 - アンケート 締切済み| 【OKWAVE】)
ギャンブル依存症との同様の治療をすれば?という意見や、子供ができると治るという意見、ペアレンタルコントロールツールを使うなどの意見があります。
中毒になる理由としては、reputationというゲーミフィケーションもさることながら、他の人から感謝されて認められるのも大きいです。
コメント書くので英語の勉強になるという言い訳も効きますし*3。
とはいえ、あまり1日何時間とかもやってしまうと生活に支障をきたすので、1日あたりの時間を制限しようと思います。