tag:crieit.net,2005:https://crieit.net/tags/Express/feed 「Express」の記事 - Crieit Crieitでタグ「Express」に投稿された最近の記事 2019-03-09T01:04:54+09:00 https://crieit.net/tags/Express/feed tag:crieit.net,2005:PublicArticle/14862 2019-03-09T01:04:54+09:00 2019-03-09T01:04:54+09:00 https://crieit.net/posts/Passport-Twitter PassportでTwitterに遷移する前に処理を行う <p>PassportでのTwitterログインは簡単にできるようになっているが、公式のマニュアルだと説明が簡単すぎて、Twitterに遷移する前に処理を入れたい場合にどうすればいいか分かりづらい。</p> <p>例えばログイン時にいたページに戻るためにパラメータにURLを渡して、ログイン後はそこに戻りたい場合。URLをセッションに保存しておきたいので処理を入れたい。</p> <p>結局authenticateはMiddlewareを返すだけなので処理をしたあとそれを呼べばよいだけ。</p> <pre><code class="javascript"> app.get('/auth/twitter', (req, res, next) => { if (req.query.back) { req.session.loginBack = req.query.back } passport.authenticate('twitter')(req, res, next) }) </code></pre> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14861 2019-03-08T00:17:46+09:00 2019-03-08T00:19:10+09:00 https://crieit.net/posts/Express-TypeORM-Wercker-CI Express+TypeORMの環境にてWerckerでCI <p>ExpressとTypeORMを使ったアプリケーションのWerckerでのCIがとりあえず完成したのでメモ。</p> <pre><code class="yaml">box: node services: - id: mysql tag: 5.7 env: MYSQL_ALLOW_EMPTY_PASSWORD: true MYSQL_DATABASE: dbname build: steps: - script: name: yarn code: yarn - script: name: Fill mysql env code: |- cp .env.example .env sudo sed -i -e "s/TYPEORM_HOST=localhost/TYPEORM_HOST=$MYSQL_PORT_3306_TCP_ADDR/g" .env sudo sed -i -e "s/TYPEORM_PORT=3306/TYPEORM_PORT=$MYSQL_PORT_3306_TCP_PORT/g" .env cp .env test/.env - script: name: migration code: yarn run migrate:run - script: name: yarn test code: yarn test </code></pre> <p>migrationとtestのコマンドは適宜自分で設定。</p> <p><code>test/.env</code>はテスト用に読むこむやつを設定しているというだけなので別のやり方をしている場合は不要。</p> <p>TypeORMは特にテスト用のDB管理方法があるわけではないようなので、事前にマイグレーションしている。(ローカル環境も考慮すると全部自分で準備しなければならないのが非常に面倒)</p> <p>ちなみにテストは下記でつくったもの。</p> <p><a href="https://crieit.net/posts/Mocha-SuperTest-async-await">Mocha+SuperTestでasync/awaitを使ってテスト</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14846 2019-02-28T23:27:00+09:00 2019-03-01T00:22:28+09:00 https://crieit.net/posts/Express-Jimp ExpressとJimpで画像を出力する <p>Node.jsでOGPを作ろうと思ったのですが、いちいちImageMagickとかインストールしたりするのが面倒だったのでnpmのパッケージをインストールするだけで使えるっぽいJimpというライブラリでの出力を試してみました。(Jimp v0.53)</p> <p>なんか公式通りにimportだと上手くいかなかったので適当にrequireしています。</p> <pre><code class="javascript">const Jimp = require('jimp') router.get('/:id/ogp.png', async (req, res) => { const font = await Jimp.loadFont(Jimp.FONT_SANS_32_BLACK) const image = await Jimp.read(600, 315, 0x0000ffff) res.type('png') image.print(font, 10, 10, 'Hello World!') image.getBuffer(Jimp.MIME_PNG, (err, data) => { res.end(data) }) }) </code></pre> <p>動作的には良さそうなんですが、まともなマニュアルがほしいですね。あとテキストはビットマップフォントのようです…。</p> <p>node-canvasの方が簡単で良さげでした。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14803 2019-02-15T23:47:25+09:00 2019-03-15T16:30:19+09:00 https://crieit.net/posts/TypeORM TypeORMの接続をセッションとして利用する <p>Expressにてセッションを使うとなると最近はRedisとかがスタンダードになっていってると思いますが、MySQLをTypeORM経由で使いたい時の備忘録です。具体的にはexpress-mysql-sessionというライブラリを使います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/chill117/express-mysql-session">chill117/express-mysql-session: A MySQL session store for the express framework in node</a></p> <p>マニュアルにも書かれている通り、ホストやポートなどを指定して接続できるのですが、アプリケーション側の接続と重複してしまうので、ORMのライブラリによる接続を共有する方法も可能となっています。</p> <p>今回TypeORM 0.2を使っているのでそちらで接続できるようにしました。まずインポート。</p> <pre><code class="javascript">const session = require('express-session') const MySQLStore = require('express-mysql-session')(session) </code></pre> <p>そしてセッションの設定です。</p> <pre><code class="javascript"> const mysqlDriver: any = <any>connection.driver app.use( session({ secret: process.env.APP_KEY, store: new MySQLStore({}, mysqlDriver.pool), resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV == 'production' } }) ) </code></pre> <p>TypeORMのConnectionがinterfaceを参照しているのか、細かいプロパティが取れないので仕方なくanyに変換してMySQLのStoreに渡すためのpoolを取得しています。(MySQLのconnectionだけじゃなくてpoolも使えます)</p> <p>あと、resaveをtrueにしているとセッション情報を上書きしまくっておかしくなるようなのでfalseにしておきました。</p> <p>まだ動かしてあまり時間が経っていないのでおかしなところなどがあればご指摘ください。また、まだTypeORMのバージョンが0.2なので変わってしまう可能性もあります。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14796 2019-02-13T21:59:32+09:00 2019-02-15T21:21:50+09:00 https://crieit.net/posts/passport-twitter-Failed-to-find-request-token-in-session passport-twitterでFailed to find request token in sessionエラー <p>久しぶりにpassport-twitterを使ってみたところ、Failed to find request token in sessionというエラーが出てログインができませんでした。</p> <p>このエラーを調べてみると、だいたいよく見つかる情報として、localhostだったのに127.0.0.1に戻って来てるから、とか、とりあえず127.0.0.1に統一しておけばいい、のような情報が見つかります。</p> <p>しかし今回はどうも統一してもうまくいかずエラーが改善しなかったため、他に原因がありそうでした。</p> <p>詳しくはわかりませんが、セッションの設定を下記のようにすることで改善しました。</p> <pre><code class="javascript">app.use( require('express-session')({ secret: process.env.APP_KEY, resave: true, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV == 'production' } }) ) </code></pre> <p>resave, saveUninitialized, cookieを上記のように指定しています。元々resaveとsaveUninitializedはデフォルトがあるだろうしそのままでいいだろう、と思って省略していたのですが、よくよく見るとビルド時に下記のようなログが出ていました。</p> <pre><code>express-session deprecated undefined resave option; provide resave option server/index.ts:46:35 express-session deprecated undefined saveUninitialized option; provide saveUninitialized option server/index.ts:46:35 </code></pre> <p>READMEに</p> <blockquote> <p>The default value is true</p> </blockquote> <p>とあったのでデフォルトがあると思っていたのですが、よくよく続きを読むと</p> <blockquote> <p>The default value is true, but using the default has been deprecated</p> </blockquote> <p>ということで無指定はdeprecatedでした。よくよく調べ直して設定してみたところうまく動くようになりました。とりあえずsaveUninitializedがデフォルトと違うようなのでこちらが問題だったのかもしれません(正直このオプションによる挙動の違いはマニュアルを見ても全くわからないのですが…)。何にしろ同様のパターンでうまくいかない方はこのあたりを調整してみると良いかもしれません。</p> <p>ちなみに、express-mysql-sessionを使用した時はresaveはtrueにしないと保存したセッション情報を何度も別のもので上書きしてしまうようでした。使用しているstoreによっても変わってくるようなので適宜確認して見る必要がありそうです。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14795 2019-02-13T20:54:02+09:00 2019-02-14T00:17:57+09:00 https://crieit.net/posts/Nuxt-js-Express-TypeORM Nuxt.js+Express+TypeORMでの環境構築でつまづいた所等の備忘録 <p>久しぶりにフルNode.jsの環境を構築したら色々ハマったので備忘録を書いておきます。とりあえずローカル環境での実行と、ローカル環境でのproductionビルドと起動が成功するまでを動くところまでの話になります。知識不足により正しい解決方法ではないものや、対応方法によっては今後また別の問題が出る可能性がある箇所などもあるかもしれませんので、あくまでも細かい部分の問題解決方法のヒントになるかも、くらいの認識で見ていただいた方が良いと思います。</p> <p>下記のような構成です。</p> <ul> <li>Nuxt.js 2.4</li> <li>Express 4</li> <li>TypeORM 0.2</li> <li>開発はts-node</li> <li>productionビルドでサーバー側はdistフォルダにビルド</li> </ul> <p>以前書いたこのあたりの記事で作ったプロジェクトの作り方と大体同じです。(この時は同様のエラーは出なかったのですが…)</p> <p><a href="https://crieit.net/posts/Nuxt-js-Express-TypeScript">Nuxt.js+ExpressのプロジェクトをTypeScript化する</a></p> <h2 id="commonjs設定にしたらエラーが出た"><a href="#commonjs%E8%A8%AD%E5%AE%9A%E3%81%AB%E3%81%97%E3%81%9F%E3%82%89%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%8C%E5%87%BA%E3%81%9F">commonjs設定にしたらエラーが出た</a></h2> <p>TypeORMの解説の通りtsconfig.jsonのmodule設定をcommonjsにしたら、vueコンポーネントの<code>export default</code>しているところで下記のようなエラーが出てビルドできなくなりました。</p> <pre><code>export 'default' … was not found </code></pre> <p>よく分からなかったのでとりあえずmoduleはes2015に戻し、nodemon.jsonでts-nodeを実行している箇所で下記のようにmoduleを上書きするようにしました。</p> <pre><code class="json"> "exec": "ts-node -O '{\"module\": \"commonjs\"}' ./server/index.ts" </code></pre> <p>あとはproductionビルド用にpackage.jsonのbuildスクリプトも下記のようにしました。</p> <pre><code class="json"> "build": "tsc --module commonjs && node copy_statics.js && cross-env NODE_ENV=production nuxt build", </code></pre> <p>これで動くならtsconfig.jsonを変えるだけで動いてくれよという感じですが…。僕の何かがおかしいだけかもしれませんのでまた時間が経ったら試してみる予定です。</p> <h2 id="productionビルド後の実行でエラー"><a href="#production%E3%83%93%E3%83%AB%E3%83%89%E5%BE%8C%E3%81%AE%E5%AE%9F%E8%A1%8C%E3%81%A7%E3%82%A8%E3%83%A9%E3%83%BC">productionビルド後の実行でエラー</a></h2> <p>TypeORMが上手く動かずエラーになってしまいました。エラーログを見るとなぜかビルドしたjsのentityファイルでなく、ビルド前のtsファイルの方をimportしていたようです。</p> <p>outDirを指定している場合だけかもしれませんが、とにかくormconfigを修正して対処しました。ormconfigはjsonファイルでなくormconfig.jsファイルとして書いています。</p> <pre><code class="javascript">let path = 'src'; if (process.env.NODE_ENV == 'production') { path = 'dist/' + path } module.exports = { entities: [ `${path}/entity/**/*{.ts,.js}` ], migrations: [ `${path}/migration/**/*{.ts,.js}` ], subscribers: [ `${path}/subscriber/**/*{.ts,.js}` ], </code></pre> <p>ただまあここは環境変数が使えるのでそちらにした方が楽そうな気がします。</p> <h2 id="CLI"><a href="#CLI">CLI</a></h2> <p>今度はCLIでもエラーが出るようになるので、こちらも-Oオプションが必要です。面倒ですね…。良い対処方法があれば教えてください。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>とりあえず開発が進められるところまでエラーをざっと取りました。他に正しい対応方法などある可能性もありそうなのでまたそのうち確認します。</p> <p>というかTypeORMいいなと思って使ってみましたがまだバージョン0.2なんですね。今後バージョンアップも必要そうですし、まだまだ色々何かしら問題が出てくるかもしれません。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14792 2019-02-12T07:22:09+09:00 2019-09-17T09:53:37+09:00 https://crieit.net/posts/Node-Chrome-Twitter NodeでChromeを操作してTwitterシェア用画像を生成するサーバー作った <p>Node.jsにはPuppeteerという画面を表示せずにChromeを扱えるライブラリがあります。それを利用し、URLをパラメータとして与えるだけでスクリーンショットを画像としてブラウザで表示させることができるサーバーアプリケーションを作成しました。</p> <p>実際にこのような画像が作成できます。例として下記はボード内投稿にコードを入れた場合に表示される画像です。</p> <p><a href="https://crieit.now.sh/upload_images/4360e4d36fd52f45541d0f4dd80cac245c56eb8b322a6.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/4360e4d36fd52f45541d0f4dd80cac245c56eb8b322a6.png?mw=700" alt="コードを表示したOGP例" /></a></p> <p>下記は記事の画像です。</p> <p><a href="https://crieit.now.sh/upload_images/597a7f2e06da1377ad7cbc486bd0f4cf5c61706d22563.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/597a7f2e06da1377ad7cbc486bd0f4cf5c61706d22563.png?mw=700" alt="記事のOGP例" /></a></p> <p>HTML, CSSから生成しているためドロップシャドウとかもこんな感じで簡単に効かせる事ができています。</p> <p>GitHubで公開していますので、とりあえず使って見たいという方は記事を読み飛ばして最後の方にあるリンクをご利用ください(こらえきれぬ涙で頬を濡らしながら)</p> <h2 id="何が便利なのか"><a href="#%E4%BD%95%E3%81%8C%E4%BE%BF%E5%88%A9%E3%81%AA%E3%81%AE%E3%81%8B">何が便利なのか</a></h2> <p>WebサービスのURLをTwitter等でシェアするだけで、URLだけでなくツイートに画像も表示されるのをよく見ると思います。</p> <p>これは画像をアップしているわけではなく、そのページ内のHTMLに書かれているメタタグで指定されている画像を勝手にSNS側で表示してくれるという便利な、いわゆるOGPというものです。</p> <h3 id="Webサービスでの困りごと"><a href="#Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A7%E3%81%AE%E5%9B%B0%E3%82%8A%E3%81%94%E3%81%A8">Webサービスでの困りごと</a></h3> <p>普通のサイトであれば画像を指定するだけですが、Webサービスの場合は投稿された内容をもとにしたOGPを動的に作りたいという場合があります。</p> <p>この場合画像を動的に作成しなければならないのですが、これがなかなか面倒です。</p> <p>サーバーサイドでやると、負荷がかかったりそもそも描画がプログラムによる命令で構築されることが多く、メンテナンスが非常に面倒です。フロントでやることもできますが、ブラウザの閲覧環境によって表示に違いが出てしまったりします。</p> <h3 id="解決策"><a href="#%E8%A7%A3%E6%B1%BA%E7%AD%96">解決策</a></h3> <p>そのため、専用のPuppeteerを用いたOGP生成サーバーを用意することで前述の全ての問題を解決することができます。</p> <p>OGPはURLを指定するだけのため、サーバーを別にすることもできますので、分けておけばアプリケーションサーバー側に負荷は一切ありません。また、OGPサーバーのブラウザで表示を行いスクリーンショットをとるため、ユーザーの環境にも依存しません。</p> <h2 id="具体的な仕組み"><a href="#%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AA%E4%BB%95%E7%B5%84%E3%81%BF">具体的な仕組み</a></h2> <p>今回作成したものは、URLを指定するとそのURLのスクリーンショットを撮って画像として表示してくれる、というだけの非常にシンプルなものです。</p> <p>例えば最初の記事の画像であれば、実際の <a href="https://crieit.net/posts/Crieit-5b91bd1569dbd">なぜCrieitを作ろうと思ったか</a> のページにアクセスし、ソースを見るとわかりますが、下記のようなメタタグがあります。</p> <pre><code class="html"> <meta property="og:image" content="https://ogp.crieit.net/posts/Crieit-5b91bd1569dbd/ogp.png"> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:image" content="https://ogp.crieit.net/posts/Crieit-5b91bd1569dbd/ogp.png"> </code></pre> <p>この<code>og:image</code>と<code>twitter:image</code>に指定されている画像が今回紹介する仕組みで作成されている画像です。</p> <p>具体的には、crieit.netの全く同じパスのスクリーンショットを撮る仕組みになっています。つまり、上記であれば下記のURLのスクリーンショットを撮っています。実際に見ていただくことも可能です。(拡張子はつけれるようにしています。理由は後述します)</p> <p><a href="https://crieit.net/posts/Crieit-5b91bd1569dbd/ogp">https://crieit.net/posts/Crieit-5b91bd1569dbd/ogp</a></p> <p>アクセスしていただくとわかりますが、OGP画像をそのまま単なるHTMLとCSSで作っているだけになります。単にそのページのスクリーンショットを撮っているだけです。この例ですとフォントが違うと思いますが、それはOGPサーバー内にインストールしているのでOGPサーバー内のChromeでは正しいフォントで表示されます。</p> <p>ちなみにブラウザでのレンダリングのため、JavaScriptも動きます。今回のコード表示の例もhighlight.jsを動かしています。</p> <h3 id="URLに拡張子"><a href="#URL%E3%81%AB%E6%8B%A1%E5%BC%B5%E5%AD%90">URLに拡張子</a></h3> <p>前述の例のように、OGPサーバー側のURLには拡張子をつけることができます。これにより、CloudflareのようなCDNを介する場合、ちゃんとキャッシュしてくれるようになります。</p> <p>つまり、そのURLに一度誰かがアクセスしていればその後はCDNで配信されるようになるため、OGPサーバー側にはアクセスが来なくなります。</p> <p>実際に前述の画像を開いてリロードするとめちゃくちゃ速いのがわかると思います。たいした処理ではないにしろ、本来はブラウザでページを開くだけの時間がかかりますので全然変わってきます。</p> <p>とはいえそもそもOGPはだいたいSNS側でキャッシュされるためあまりメリットは大きくないのですが、改造すれば他の用途にも使用することができますので、この仕組みを覚えておけば色々と便利ではあると思います。</p> <h3 id="構成要素"><a href="#%E6%A7%8B%E6%88%90%E8%A6%81%E7%B4%A0">構成要素</a></h3> <p>Puppeteerを使うのでNode.jsのサーバーとなります。</p> <p>サーバーはExpressで、expressコマンドで作成したソースのほとんどそのままです。複雑なことはしていないので数十行書いたくらいで完成しました。(そのため不要な処理もたくさん残っていて作り自体は雑です…)</p> <h3 id="functions系の利用は断念"><a href="#functions%E7%B3%BB%E3%81%AE%E5%88%A9%E7%94%A8%E3%81%AF%E6%96%AD%E5%BF%B5">functions系の利用は断念</a></h3> <p>現在色々なPaasやfunctionsサービスがありますが、色々調べたり試した結果利用は断念し、最終的にシンプルにExpressによるNode.jsのサーバーで作ることにしました。</p> <p>断念した理由は下記等です。</p> <ul> <li>リクエスト毎にブラウザを起動しなければならないため、画像生成にかなり時間がかかりそう。Expressであれば最初に一度起動しておくだけでよい</li> <li>サービスによってはそもそも使えない</li> <li>サービス上では特殊な実装にしなければならない場合があるので、ローカルで試しづらく開発に時間がかかって面倒な場合がある</li> <li>サービスによってはプロセスの実行時間制限があったりする</li> </ul> <p>…等です。もしうまく簡単に動かせるサービスがあったらぜひ教えてください。</p> <h2 id="サーバー"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC">サーバー</a></h2> <p>実際のサーバーの作り方を解説していきます。</p> <h3 id="Chromeをインストール"><a href="#Chrome%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Chromeをインストール</a></h3> <p>一番重要な仕事をしてくれるものです。</p> <p>ChromeDriverを試していた時の名残で一部不要なものが混じってるかもしれません。これはDockerfileの中身そのままです。</p> <pre><code class="sh">apt-get update apt-get install -y libappindicator1 fonts-liberation unzip curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb dpkg -i google-chrome*.deb || apt update && apt-get install -f -y </code></pre> <h3 id="フォントをインストール"><a href="#%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">フォントをインストール</a></h3> <p>Webフォントだけでページを作れば不要ですが、そうでない場合や不足のフォントがありそうな場合にはインストールしておきます。</p> <pre><code class="sh">apt-get install fonts-ipafont-gothic fonts-ipafont-mincho </code></pre> <h3 id="Node.jsをインストール"><a href="#Node.js%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Node.jsをインストール</a></h3> <p>とりあえずnpmとyarnを入れておきます。公式サイトに書かれている手順で入れておけば問題ないと思います。</p> <h3 id="npmのモジュールーインストール"><a href="#npm%E3%81%AE%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%83%BC%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">npmのモジュールーインストール</a></h3> <p>Chromeはすでにインストールしているため、それをスキップする形でインストールします。違うパターンでインストールしたい場合は適宜変更してください。</p> <pre><code class="sh">PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true yarn </code></pre> <h3 id="Nginxをインストール"><a href="#Nginx%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Nginxをインストール</a></h3> <p>これは必須ではないのですが、ポート3000で動いているNode.jsサーバーを80にプロキシしたかったのと、SSLに対応したかったため同じくLet's Encryptのcertbotが使用するURLもプロキシして放置で証明書が更新されるようにしたかったためNginxで動かすようにしています。(ここもわりと適当なため適宜変更してください)</p> <pre><code> listen 443 ssl default_server; ssl_certificate /etc/letsencrypt/live/ogp.example.net/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/ogp.example.net/privkey.pem; server_name ogp.example.net; proxy_redirect off; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; location ^~ /.well-known/ { root /var/www/html; } location / { proxy_pass http://localhost:3000/; } </code></pre> <h3 id="Systemdでサービス化"><a href="#Systemd%E3%81%A7%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E5%8C%96">Systemdでサービス化</a></h3> <p>下記のように設定しました。</p> <pre><code class="ini">[Unit] Description=ogp server After=syslog.target network.target [Service] Type=simple ExecStart=/usr/bin/npm start WorkingDirectory=/path/to/puppeteer-ogp KillMode=mixed Restart=always User=yourname Group=yourgroup [Install] WantedBy=multi-user.target </code></pre> <p>だと、理由はまだよく分かりませんがうまくアプリケーションやChromeのプロセスが終了してくれないため、<code>KillMode=mixed</code>にして丸々強制終了するようにしています。</p> <p>サービスの有効化は下記です。</p> <pre><code class="sh">sudo systemctl enable ogp </code></pre> <p>あとは下記で起動すれば完全放置できます。</p> <pre><code class="sh">sudo service ogp start </code></pre> <p>ちなみに僕は無料枠で動かせるGCE(Google Compute Engine)のf1-microを使っています。</p> <h2 id="公開しました"><a href="#%E5%85%AC%E9%96%8B%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F">公開しました</a></h2> <p>かなり何もやってないレベルの製作物だったので、作成したものをGitHubで公開しています。是非適当に試してみたり、改造して遊んでみたりしてみてください。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dala00/puppeteer-ogp">dala00/puppeteer-ogp</a></p> <p>下記に重要な注意点を書きます。そのあとローカルでの開発方法もざっと書いておきます</p> <h2 id="注意点"><a href="#%E6%B3%A8%E6%84%8F%E7%82%B9">注意点</a></h2> <p>まだ動かしてからさほど経っていないため、どれくらい安定稼働してくれるかは謎です。というのも、アプリケーションとは別にChromeが動いているので、そのあたりで安定性がどうなのかという部分が全く想像できません。一応最後に調整をしてからは問題ないようなのですが、稼働初期はクラッシュしまくっていました。</p> <p>ですので利用される場合は自己責任でよろしくお願いします。一応どういう対策を入れたかと、どういう問題がありそうかをメモしておきます。</p> <h3 id="エラー処理を入れる"><a href="#%E3%82%A8%E3%83%A9%E3%83%BC%E5%87%A6%E7%90%86%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B">エラー処理を入れる</a></h3> <p>ブラウザがクラッシュする前提で、ページを開くときとかにエラーをcatchするようにし、問題があれば再起動するようにします。ただ、確認が不十分のため現在のエラー処理自体が正しくない可能性があります。実際にクラッシュするとプロセスがどんどん増えていく問題を確認済みです。</p> <p>おそらくですが、デーモン化して確実に再起動される状態であれば、問題が出たらそのままアプリケーションを落としてしまう方が安心では、という気がしています。(そうなると安定性と高頻度アクセスを保証すべきようなアプリケーションでは難しいかもしれませんが)</p> <h3 id="URLのフィルタを入れる"><a href="#URL%E3%81%AE%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B">URLのフィルタを入れる</a></h3> <p>これが今のところ一番の改善になりました。というのも、サーバーを公開していると.gitフォルダやphpMyAdminを無差別に探してデータを盗もうとする不審なアクセスがあります。これが一瞬のあいだに高頻度で行われるのでChromeへの負荷が高まりクラッシュしていました。</p> <p>そのため下記のような感じで環境変数にて必要なURLだけをフィルタできるようにし、それ以外のURLの場合はChromeを使わないようにしました。これだけでクラッシュが発生しなくなりました。</p> <pre><code>URL_FILTER=/articles,/posts </code></pre> <p>とにかく、いかにChromeを使用しないか、というところが重要になってくる気がします。アクセスが多い場合、待機させるようにして同時にたくさんのページを開かないような形にすると安定性が上る可能性もありそうです。(ただしレスポンスが悪くなるとSNSがOGPを認識してくれない可能性も出てきそうですが)</p> <h3 id="キューで処理する(追記)"><a href="#%E3%82%AD%E3%83%A5%E3%83%BC%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B%EF%BC%88%E8%BF%BD%E8%A8%98%EF%BC%89">キューで処理する(追記)</a></h3> <p>来たリクエストをすぐ処理せず、キューで処理する形式にしてサーバー全体で一度に1ページしか開かないようにしたところ安定している気がします。GitHubのプログラムもそのように更新済みです。</p> <h3 id="多分結構スペックが高いサーバーの方が良さそう"><a href="#%E5%A4%9A%E5%88%86%E7%B5%90%E6%A7%8B%E3%82%B9%E3%83%9A%E3%83%83%E3%82%AF%E3%81%8C%E9%AB%98%E3%81%84%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE%E6%96%B9%E3%81%8C%E8%89%AF%E3%81%95%E3%81%9D%E3%81%86">多分結構スペックが高いサーバーの方が良さそう</a></h3> <p>Chrome自体が低スペック環境でガンガン快適に動くようなものではないので、サーバーもスペックが高いほうが良さそうな気がします。実際に現在使っているGoogle Compute Engineのf1-microインスタンスでは、マシンタイプをグレードアップしたほうが良い、というアラートがずっと出っぱなしになっています。</p> <p>また、おそらくスペックが高いほうがクラッシュする可能性も低くなるのでは…と想像しています。</p> <h3 id="参考にした情報"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E6%83%85%E5%A0%B1">参考にした情報</a></h3> <p>たしか下記あたりを参考にしました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://uyamazak.hatenablog.com/entry/2019/01/30/150910">Headless Chromeを使ったPDF変換サーバーが落ちないようにした対策まとめ - GAミント至上主義</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/go_sagawa/items/4a368040fac6f7264e2c">puppeteerを永続化したNode.jsアプリ内で安定稼働させる方法 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/windyakin/items/00b085902547570eebc6">Docker上のpuppeteerがPage crashしてしまうときはshmサイズを疑う - Qiita</a></li> </ul> <h2 id="ローカルでの開発方法"><a href="#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%81%A7%E3%81%AE%E9%96%8B%E7%99%BA%E6%96%B9%E6%B3%95">ローカルでの開発方法</a></h2> <p>基本的にはGitHubにあげているREADMEそのままですが、Dockerとdocker-composeさえ入っていれば誰でもすぐ試せるようになっています。Getting StartedとDevelopmentのところをそのまま行うだけで動きます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>ちょっと改造すればひとつのサーバーで複数のサービスのURLに対応することも可能ですし、特に個人開発している人はひとり一OGPサーバーを持っておくとなにかと便利な気がします。</p> <p>コードのOGPはよろしければ下記で色々投稿して試してみてください!</p> <p><a href="https://crieit.net/boards/try-code-ogp">コードのOGPを試してみるボード</a></p> <p>もし問題やプルリクエストがある場合はお気軽にGitHubに立ててください。(余裕のあるときしか見れないかもしれませんが…)</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14519 2018-08-27T21:01:24+09:00 2018-11-28T11:31:08+09:00 https://crieit.net/posts/Nuxt-js-Express-TypeScript Nuxt.js+ExpressのプロジェクトをTypeScript化する <p>create-nuxt-appでNuxt.js+Expressのプロジェクトは簡単に作ることができますが、それで作成したプロジェクトをTypeScript化してみました。</p> <p>TypeScript化の対応は、Nuxt.js側、Express側で別々となります。Express側はちょっと面倒なので、とりあえずNuxt.jsだけ先にやっておく、とかでも良いと思います。</p> <p>Nuxt.js+Expressプロジェクトの作成は下記で紹介しているexpress-templateを利用おり、今回の記事はそちらの前提の説明となります。</p> <p><a href="https://crieit.net/posts/Nuxt-Express">Nuxt+Expressのプロジェクト作成で良さそうなのは?</a></p> <p>※ 2018/8現在の情報です。ところどころNuxt.js1の情報が混じっていますので、下記も合わせて確認が必要な箇所があります。</p> <p><a href="https://crieit.net/posts/Nuxt-js-Nuxt-js2">Nuxt.jsのプロジェクトをNuxt.js2にバージョンアップしてみた</a></p> <h2 id="Nuxt.js側をTypeScript化する"><a href="#Nuxt.js%E5%81%B4%E3%82%92TypeScript%E5%8C%96%E3%81%99%E3%82%8B">Nuxt.js側をTypeScript化する</a></h2> <p>基本的には、create-nuxt-appのtypescript-templateで実装されているものを参考にしています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/nuxt-community/typescript-template">nuxt-community/typescript-template: Typescript starter with Nuxt.js</a></p> <h3 id="TypeScript関連をインストール"><a href="#TypeScript%E9%96%A2%E9%80%A3%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">TypeScript関連をインストール</a></h3> <p>とりあえずTypeScriptをインストールします。</p> <pre><code class="sh">yarn add --dev typescript [email protected] </code></pre> <p>ts-loaderは3.5.0のバージョンを指定します(Nuxt.js2の場合は4)。無指定だとこのプロジェクトに合わないバージョンが入ってエラーになってしまいます。</p> <p>あとはNuxt.jsとVueをTypeScriptと連携させるライブラリも入れておきます。</p> <pre><code class="sh">yarn add nuxt-property-decorator </code></pre> <h3 id="設定ファイルを準備"><a href="#%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%BA%96%E5%82%99">設定ファイルを準備</a></h3> <p>先程の <a target="_blank" rel="nofollow noopener" href="https://github.com/nuxt-community/typescript-template">typescript-template</a> のリポジトリから、下記のファイルをそのままコピーしてきます。</p> <ul> <li>index.d.ts</li> <li>tsconfig.json</li> <li>modules/typescript.js</li> </ul> <p>そして、nuxt.config.jsのmodulesに<code>"~/modules/typescript.js"</code>を追加しておきます。</p> <p>基本的にはこれで完了です。あとは実際に各コンポーネントをTypeScriptにしていくだけです。</p> <h3 id="VueコンポーネントをTypeScript化する"><a href="#Vue%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92TypeScript%E5%8C%96%E3%81%99%E3%82%8B">VueコンポーネントをTypeScript化する</a></h3> <p>とりあえず簡単なコンポーネントのサンプルです。JavaScriptのままでも動きますので、必要なコンポーネントから順次TypeScriptに変更していくことも可能です。</p> <pre><code class="html"><template> <div> <span>{</span><span>{</span> name <span>}</span><span>}</span> <button @click="increment">Click</button> <span>{</span><span>{</span> cnt <span>}</span><span>}</span> <sub-component></sub-component> </div> </template> <script lang="ts"> import { Component, Vue, Prop } from "nuxt-property-decorator"; import SubComponent from "./SubComponent.vue"; @Component({ components: { SubComponent } }) export default class extends Vue { @Prop({ type: String, required: true }) name: string; cnt = 0; increment() { this.cnt++; } } </script> </code></pre> <h4 id="scriptタグ"><a href="#script%E3%82%BF%E3%82%B0">scriptタグ</a></h4> <p>scriptタグは<code>lang="ts"</code>を追記します。</p> <h4 id="Vueクラスを使う"><a href="#Vue%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86">Vueクラスを使う</a></h4> <p>JavaScriptの場合はオブジェクトをexportしていましたが、TypeScriptの場合はclassを使うため、Vueクラスを継承したクラスを作成してそれをexportします。</p> <pre><code class="javascript">export default class extends Vue { </code></pre> <h4 id="Componentデコレータを使う"><a href="#Component%E3%83%87%E3%82%B3%E3%83%AC%E3%83%BC%E3%82%BF%E3%82%92%E4%BD%BF%E3%81%86">Componentデコレータを使う</a></h4> <p>TypeScriptの場合は、クラス内にコンポーネントの設定を行うのではなく、<code>@Component</code>デコレータを追加してそちらに設定を追加します。</p> <pre><code class="javascript">@Component({ components: { SubComponent } }) </code></pre> <h4 id="ステートはプロパティとして定義"><a href="#%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88%E3%81%AF%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%A8%E3%81%97%E3%81%A6%E5%AE%9A%E7%BE%A9">ステートはプロパティとして定義</a></h4> <p>コンポーネントが保持するステートは<code>data</code>メソッドを使う必要がなく、直接クラスのプロパティとして定義します。もちろん型も指定できます。配列やオブジェクトの場合は型を指定しておかないとメソッド内で使う場合にエラーになったりします。</p> <pre><code class="javascript">cnt = 0; item: Item; </code></pre> <h4 id="プロパティはPropを使う"><a href="#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AFProp%E3%82%92%E4%BD%BF%E3%81%86">プロパティはPropを使う</a></h4> <p>呼び出し元から渡されるVueコンポーネントのプロパティは<code>@Prop</code>を使ってクラスのプロパティとして定義します。<code>@Prop</code>は<code>@Prop(Number)</code>のようにもできますし、色々と指定方法があります。</p> <h4 id="メソッドはクラスのメソッド"><a href="#%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AF%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89">メソッドはクラスのメソッド</a></h4> <p>その他はだいたい一緒です。メソッドはクラスのメソッドになるため、カンマ等で区切る必要はありません。</p> <h2 id="Express側をTypeScript化する"><a href="#Express%E5%81%B4%E3%82%92TypeScript%E5%8C%96%E3%81%99%E3%82%8B">Express側をTypeScript化する</a></h2> <p>Express側はちょっと面倒なので、気分が乗ったらやる、くらいの感じでも良いかも知れません。シンプルなルーティングの処理がメインだと恩恵も少ないかもしれません。</p> <p>そもそも開発時とリリースビルド時でも処理が異なるため、両方で動かせるように気をつけながら対応していく必要があります。また、色々な方法がありそうだったため、拘る人は自分で色々と調べて実装してみたほうが良いかも知れません。今回はとりあえずやってみて動いた方法を紹介します。</p> <p>ちなみに本番環境の手順については、一応ローカルでは動いていますが実際の本番環境では未構築のためまだ何か問題が出る可能性もありますのでご注意ください。何か見つかり次第等記事に追記していきます。</p> <h3 id="開発時の設定"><a href="#%E9%96%8B%E7%99%BA%E6%99%82%E3%81%AE%E8%A8%AD%E5%AE%9A">開発時の設定</a></h3> <p>前述のテンプレートだと、開発時はnodemonを利用してサーバーを起動&ホットリロードを実現しています。開発時はある程度適当でも問題ないと思いますので、nodemonでts-nodeを使えるようにします。ts-nodeというのはNodeのスクリプトを実行する時に、直接TypeScriptを実行できるようにするコマンドです。</p> <h4 id="ts-nodeを設定"><a href="#ts-node%E3%82%92%E8%A8%AD%E5%AE%9A">ts-nodeを設定</a></h4> <p>ts-nodeの設定は下記を参考にしました。</p> <p><a target="_blank" rel="nofollow noopener" href="http://aknow2.hatenablog.com/entry/2018/04/09/150006">nodejs + TypeScriptでサーバーサイドを開発している時に、コードを編集したら自動リロードさせる。</a></p> <p>まずはts-nodeをインストールします。</p> <pre><code class="sh">yarn add --dev ts-node </code></pre> <p>nodemonでtsファイルを使えるように設定ファイルを追加します。</p> <p>nodemon.json</p> <pre><code class="json">{ "watch": ["server", "routes"], "ext": "ts", "exec": "ts-node ./server/index.ts" } </code></pre> <p>package.jsonの<code>dev</code>スクリプトで、<code>nodemon server/index.js</code>としている部分を<code>nodemon server/index.ts</code>のように拡張子を変更します。</p> <p>あとは<code>server/index.js</code>の拡張子をtsに変更すればOKです。</p> <p>基本的にはこれで<code>dev</code>スクリプトは動くと思うのですが、他の各ファイルのグローバルな場所で同じ変数(router等)が定義されているとエラーになってしまうようなので、クラス化したり変数名を変えるか、下記のようにファイル全体を中括弧で囲んでおくかします。</p> <pre><code class="javascript">{ const { Router } = require("express"); const router = Router(); router.get("/logout", function(req, res) { req.logout(); res.redirect("/"); }); app.use("/auth", router); } </code></pre> <h4 id="他のファイルもTypeScript化する"><a href="#%E4%BB%96%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%82TypeScript%E5%8C%96%E3%81%99%E3%82%8B">他のファイルもTypeScript化する</a></h4> <p>その他のルーティング用ファイルなども、基本的には拡張子をtsに変更すれば動きます。<code>server/index.ts</code>からそれらの読み込む場合などに、拡張子の指定が必要になります。</p> <p>あとは定義されていない変数などを使用するとエラーとして検出してくれたりしますので、使っていない処理などがあってエラーになってしまう場合は適当に修正しておいてください。</p> <p>また、オブジェクトも適当なプロパティを途中で追加したりするとエラーになりますので、変数を定義する場合にany等をつけておいてください。</p> <pre><code class="typescript">const where: any = { deleted_at: null }; where.keyword = req.params.keyword; </code></pre> <h3 id="本番ビルド用の設定"><a href="#%E6%9C%AC%E7%95%AA%E3%83%93%E3%83%AB%E3%83%89%E7%94%A8%E3%81%AE%E8%A8%AD%E5%AE%9A">本番ビルド用の設定</a></h3> <p>そのままビルドするとtsファイルの横に大量にjsファイルが作られてしまうため、適当にフォルダを作ってそちらにビルドされるようにします。</p> <p>tsconfig.json</p> <pre><code class="json"> "outDir": "./dist" </code></pre> <p>あとは設定ファイルやpackage.jsonも上記フォルダに無いとエラーになってしまうため、スクリプトを作ってコピーするようにしておきます。</p> <p>copy_statics.js</p> <pre><code class="javascript">const fs = require("fs-extra"); const targets = ["config", "package.json"]; targets.forEach(path => { fs.copySync(path, `dist/${path}`); }); </code></pre> <p>あとはtsファイルのトランスパイル、コピースクリプトの実行を実行し、トランスパイルされたjsファイルを実行するようにビルド&起動スクリプトを変更します。(startはbuildを実行してからになります)</p> <p>package.json</p> <pre><code class="json"> "build": "tsc && node copy_statics.js && nuxt build", "start": "cross-env NODE_ENV=production node dist/server/index.js", </code></pre> <p>あと、本番ビルドのフォルダの関係でrequireやimportで<code>.ts</code>のように拡張子をつけている場合はエラーになるため、拡張子は削除しておきましょう。Sequelizeの models/index.ts の中身も変更しておきます。</p> <p>before</p> <pre><code class="javascript"> return ( file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".ts" ); </code></pre> <p>after</p> <pre><code class="javascript"> const ext = file.slice(-3); const validExt = ext === ".ts" || ext === ".js"; return file.indexOf(".") !== 0 && file !== basename && validExt; </code></pre> <p>これで開発環境、本番環境共通のファイルでTypeScriptが動かせるようになっていると思います。</p> <h3 id="importがエラーになる場合"><a href="#import%E3%81%8C%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AB%E3%81%AA%E3%82%8B%E5%A0%B4%E5%90%88">importがエラーになる場合</a></h3> <p>開発サーバー実行時にサーバー側のみに限りimportを行うとエラーになると思いますので、その場合はnodemon.jsonのスクリプトを下記の様に変更するとimportできるようになります。</p> <pre><code class="json"> "exec": "ts-node -O '{\"module\": \"commonjs\"}' ./server/index.ts" </code></pre> <p>本番もざっと検証してみましたが、やはりtsconfigの方もcommonjsでないと動きませんでした。あと、postcss関連のエラーが出るようなのでNuxt1の場合はNuxt2にアップグレードが必要です。</p> <p>参考<br /> <a href="https://crieit.net/posts/Nuxt-js-Nuxt-js2">Nuxt.jsのプロジェクトをNuxt.js2にバージョンアップしてみた</a></p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>まだ開発中のプロジェクトで試した方法を投稿しただけになりますので、場合によってはうまく動かないパターンなどが出るかもしれません。何か見つかり次第また追記していきます。</p> <p>とりあえず未定義の変数を使う場合はエラーになったりしますので、typoを減らしたりという効果はすぐ出そうです。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14512 2018-08-21T22:09:13+09:00 2018-10-27T17:52:56+09:00 https://crieit.net/posts/Express Expressのセッションにファイルを使う <p>Expressでアプリケーションを作る場合、express-sessionを使うことで簡単にセッションを利用できます。ただ、これはインメモリのセッションのため、サーバーを再起動するとセッションが消えてしまいます。そのため、ファイルのセッションを試してみました。</p> <p>※ 引き続き検証中ですが状況によってうまくセッションが読み込めなかったりしています。</p> <h2 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h2> <ul> <li>Express 4.15</li> <li>express-session 1.15</li> </ul> <h2 id="session-file-store"><a href="#session-file-store">session-file-store</a></h2> <p>下記のライブラリを使いました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/valery-barysok/session-file-store">valery-barysok/session-file-store: Session file store is a provision for storing session data in the session file</a></p> <h3 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h3> <pre><code class="sh">yarn add session-file-store </code></pre> <h3 id="設定"><a href="#%E8%A8%AD%E5%AE%9A">設定</a></h3> <p>元々あったセッションの設定を下記のようにsession-file-storeを使う形に変更します。</p> <pre><code class="javascript">const session = require("express-session"); const FileStore = require("session-file-store")(session); app.use( session({ secret: "your secret", store: new FileStore({ ttl: 30 * 86400 }), expires: new Date(253402300000000), resave: true, saveUninitialized: false }) ); </code></pre> <p>僕が試した時は、resaveを設定しないと(最初だけ?)セッションが保存されませんでした。(とはいえresaveはデフォルトtrueとマニュアルにも書かれていますが…謎です)</p> <p>もしかすると有効期限など、他の設定と影響したりしても挙動が変わってくるかもしれません。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>Expressのプロジェクトでファイルに保存するセッションを試してみました。</p> <p>実際はデータベースに保存したりする方が良いとは思います。でも一応<code>mweibel/connect-session-sequelize</code>というパッケージはあったのですが、最新のSequelizeでは動作しないようでした。あまりそのあたりのパッケージは充実していないのでしょうか。</p> <p>あとは速さを求めるならそのままインメモリのままでも良いのかもしれません。何かでデーモンが再起動してしまった時のことなどが気にはなりますが。</p> <p>そういう面でもファイルは割とお手軽で良いと思います(まだがっつり動作確認はできていませんが)。不安な場合等はとりあえず開発環境だけセッションをファイルで保存するようにしておくと良いかもしれませんね。サーバーが再起動する度にログイン状態が消えたりするのは結構面倒ですし。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14506 2018-08-16T12:02:02+09:00 2018-10-30T15:42:25+09:00 https://crieit.net/posts/Nuxt-js-Express-Twitter Nuxt.js+Expressで簡単にTwitter認証 <p>Nuxt.jsとExpressでアプリケーションを作成する場合のTwitter認証でのログインを試してみました。Node.jsでのTwitter認証はPassportというライブラリを使用すると非常に簡単でした。細かく検証はしていませんがSSR(サーバーサイドレンダリング)の場合でも問題ないと思われます。</p> <p>このあたりで作ったプロジェクトをベースにして試しています。</p> <p><a href="https://crieit.net/posts/Nuxt-js-Express-ORM">Nuxt.js+Expressでとにかく簡単にORM</a></p> <ul> <li>Nuxt.js v1.4.2</li> <li>Passport-twitter v1.0.4</li> </ul> <h2 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h2> <p>何はともあれまずはインストールします。Passport本体とTwitter用の拡張です。</p> <p>基本的なところは公式のマニュアル通りですので、よくわからなくなった場合やバージョンが違う場合はそちらを参考にしてください。</p> <p><a target="_blank" rel="nofollow noopener" href="http://www.passportjs.org/docs/twitter/">Documentation: Twitter</a></p> <pre><code class="sh">yarn add passport passport-twitter </code></pre> <h2 id="初期設定"><a href="#%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A">初期設定</a></h2> <h3 id="Passportの設定"><a href="#Passport%E3%81%AE%E8%A8%AD%E5%AE%9A">Passportの設定</a></h3> <p>まずはplugins/passport.jsとして初期化スクリプトを作成します。<code>TWITTER_CONSUMER_KEY</code>等は環境変数として指定しておいてください。(dotenv等を使うと便利です)</p> <p>下記はSequelizeでユーザーデータとして保存する場合のサンプルも入れています。</p> <pre><code class="javascript">var passport = require("passport"), TwitterStrategy = require("passport-twitter").Strategy; module.exports = function(app) { const models = app.get("models"); const User = models.User; passport.use( new TwitterStrategy( { consumerKey: process.env.TWITTER_CONSUMER_KEY, consumerSecret: process.env.TWITTER_CONSUMER_SECRET, callbackURL: `${process.env.APP_URL}/auth/twitter/callback`, includeEmail: true // メールアドレスが必要な場合 }, async function(token, tokenSecret, profile, done) { let user = await User.findOne({ where: { twitter_id: profile.id } }); if (!user) { user = User.build({ unique_id: profile.username, name: profile.displayName, email: profile.emails[0].value, location: profile._json.location, bio: profile._json.description, url: profile._json.url, image: profile.photos[0].value, twitter_id: profile.id, twitter_token: token, twitter_secret: tokenSecret }); await user.save(); } done(null, user.get({ plain: true })); } ) ); passport.serializeUser(function(user, done) { done(null, user); }); passport.deserializeUser(function(user, done) { done(null, user); }); return passport; }; </code></pre> <h3 id="認証用ルーティング"><a href="#%E8%AA%8D%E8%A8%BC%E7%94%A8%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0">認証用ルーティング</a></h3> <p>認証用のルーティングを作成します。今回はroutes/auth.jsとしてファイルを分けました。アクションにそのままPassportの処理を指定するようなので下記のようにしてルーティング設定全体を関数化して読み込む形にしました。</p> <pre><code class="javascript">const { Router } = require("express"); module.exports = function(app, passport) { const router = Router(); router.get("/twitter", passport.authenticate("twitter")); router.get( "/twitter/callback", passport.authenticate("twitter", { successRedirect: "/", failureRedirect: "/" }) ); app.use("/auth", router); }; </code></pre> <h3 id="アプリケーションに組み込み"><a href="#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF">アプリケーションに組み込み</a></h3> <p>これらをserver/index.jsで読み込んでアプリケーションに組み込みます。</p> <pre><code class="javascript">const passport = require("../plugins/passport")(app); app.use( session({ secret: "your secret" }) ); app.use(passport.initialize()); app.use(passport.session()); require("../routes/auth")(app, passport); </code></pre> <p>基本的な部分はこれで完了です。あとは実際に動かしていくための処理を入れていきます。</p> <h2 id="Nuxt.jsと連携させる"><a href="#Nuxt.js%E3%81%A8%E9%80%A3%E6%90%BA%E3%81%95%E3%81%9B%E3%82%8B">Nuxt.jsと連携させる</a></h2> <p>PassportでのログインはExpress側のセッションに保存しているだけのため、これをNuxt.js側にも渡します。アプリケーション全体に絡むことのため、Storeを使ってNuxt.js上のどこからでもアクセスできるようにします。Nuxt.jsの場合は下記のように適当にファイルを一つ追加するだけで簡単にStoreが利用できます。</p> <p>store/index.js</p> <pre><code class="javascript">export const state = () => ({ authUser: null }); export const mutations = { setUser(state, authUser) { state.authUser = authUser; } }; export const actions = { nuxtServerInit({ commit }, { req }) { if (req.session.passport && req.session.passport.user) { commit("setUser", req.session.passport.user); } } }; </code></pre> <p>Storeの場合<code>setUser</code>でなく<code>SET_USER</code>が使われることが多い気がします。</p> <p>あとはもう下記のようにゆるふわな感じで適当にログインしているか判断できます。</p> <pre><code class="html"> <div v-if="$store.state.authUser"> <img :src="$store.state.authUser.image"> </div> <span v-if="!$store.state.authUser"> <a href="/auth/twitter">ログイン</a> </span> </code></pre> <p>スクリプト側でも<code>this.$store.state.authUser</code>みたいな感じで利用できます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>荒い感じの書き方も一部あるかもしれませんが、とりあえず非常に簡単にTwitterログインを行ってNuxt.jsと連携させることができました。また動きのおかしいところなどあれば随時追記していきます。</p> <p>他にもGoogleアカウントのログインなども公式のドキュメントに書かれています。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14502 2018-08-10T23:09:11+09:00 2019-05-03T09:19:45+09:00 https://crieit.net/posts/Nuxt-js-Express-ORM Nuxt.js+Expressでとにかく簡単にORM <p>Nuxt.jsとExpressを使ってアプリケーションを作るとなると、やはりMySQL等を使うためにORMのライブラリが欲しくなります。Node.jsにはSequelizeというORMのライブラリがあり、それを使って簡単にデータベースを使うことが可能です。</p> <h2 id="趣旨"><a href="#%E8%B6%A3%E6%97%A8">趣旨</a></h2> <p>Sequelizeの使い方の説明は他のサイトの記事などがもっと参考になります。ただ、とりあえずすぐ簡単に使う、という趣旨の記事があまりなさそうだったのでそのあたりに重点を置いて書いてみます。</p> <p>ちなみに下記で作ったプロジェクトの続きとして作っています。<br /> <a href="https://crieit.net/posts/Nuxt-Express">Nuxt+Expressのプロジェクト作成で良さそうなのは?</a></p> <h2 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h2> <p>インストールはマニュアル通りです。npmを使う場合はマニュアルを見てください。</p> <p><a target="_blank" rel="nofollow noopener" href="http://docs.sequelizejs.com/manual/installation/getting-started.html">Installation | Sequelize | The node.js ORM for PostgreSQL, MySQL, SQLite and MSSQL</a></p> <pre><code class="sh">// Using Yarn $ yarn add sequelize # And one of the following: $ yarn add pg pg-hstore $ yarn add mysql2 $ yarn add sqlite3 $ yarn add tedious // MSSQL </code></pre> <h2 id="初期化"><a href="#%E5%88%9D%E6%9C%9F%E5%8C%96">初期化</a></h2> <p>引き続き上記のマニュアルを見て初期化…ではなく、そこは飛ばして下記の通りにやっていきます。</p> <p><a target="_blank" rel="nofollow noopener" href="http://docs.sequelizejs.com/manual/tutorial/migrations.html">Tutorial | Sequelize | The node.js ORM for PostgreSQL, MySQL, SQLite and MSSQL</a></p> <p>どういうことかというと、最初のリンクの説明どおりに設定していかなくても、Sequelizeにはsequelize-cliというものもあり、それを使えばなんでもかんでもシェルでコマンド実行するだけで簡単にできてしまいます。</p> <h3 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h3> <p>とりあえずはsequelize-cliをインストールします。(npmの場合はマニュアルどおりです)</p> <p>グローバルにインストールしておけば実行時にパスの指定が必要なくなります。</p> <pre><code>yarn add sequelize-cli </code></pre> <h3 id="初期化"><a href="#%E5%88%9D%E6%9C%9F%E5%8C%96">初期化</a></h3> <p>下記コマンドで初期化します。</p> <pre><code>node_modules/.bin/sequelize init </code></pre> <p>これでなんか必要なファイルを全部作ってくれます。</p> <p>config/config.jsonというファイルにデータベースへの接続情報設定がありますのでそちらに適宜入力していきます。とりあえず最初に試す時は直接入力してしまってOKです。</p> <p>時間のある時にでも生成されたmodels/index.jsを見ればわかりますが、config.jsonにuse_env_variableの設定を入れたりすると環境変数から読み込んだりもできるので、コミットしたり本番環境を作ったりする場合はそのあたりが使えると思います。</p> <h2 id="モデルとマイグレーションファイルを作成する"><a href="#%E3%83%A2%E3%83%87%E3%83%AB%E3%81%A8%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">モデルとマイグレーションファイルを作成する</a></h2> <p>ORMのため、マイグレーションファイルを作ってデータベースにテーブルを登録できるようにし、モデルを作ってアプリケーション上でデータを操作できるようにします。</p> <p>これもコマンドで簡単に作成できます。下記は本家に書かれている例です。</p> <pre><code class="sh">node_modules/.bin/sequelize model:generate --name User --attributes firstName:string,lastName:string,email:string </code></pre> <p>これでマイグレーションファイルとモデルのファイルが作成されます。createdAt, updatedAtというタイムスタンプのカラムも自動的に追加されます。このあたりは<code>--underscored</code>オプションを付けると<code>created_at</code>, <code>updated_at</code>のようにスネークケースで作成できます。他のフレームワーク等との互換性を考えるのであれば指定しましょう。</p> <h3 id="マイグレーションを実行"><a href="#%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E5%AE%9F%E8%A1%8C">マイグレーションを実行</a></h3> <pre><code class="sh">node_modules/.bin/sequelize db:migrate </code></pre> <p>これで接続情報などが間違っていなければテーブルが作成されているはずです。</p> <p>ちなみに僕はDBまわりだけdocker-composeを使って用意しています。(もちろんローカル等でデータベースが動いている場合には不要です)</p> <pre><code class="yaml">version: '2' volumes: mysql_data: driver: local services: mysql: image: mysql:8.0 volumes: - mysql_data:/var/lib/mysql ports: - 3308:3306 environment: MYSQL_ALLOW_EMPTY_PASSWORD: "true" phpmyadmin: image: phpmyadmin/phpmyadmin environment: - PMA_ARBITRARY=1 - PMA_HOST=mysql - PMA_USER=root - PMA_PASSWORD= ports: - 8102:80 </code></pre> <p>よく分からない人はmysqlのバージョンを8ではなく5.6とか5.7にしてください。そうすれば<code>http://localhost:8102</code>でphpmyadminも使えるので簡単にデータベースが作成できます。動いてすっきりしたあとにでもまた調べてバージョンを上げてみてください。(ヒント:mysql_native_password)</p> <p>この場合接続設定はこんな感じです。</p> <pre><code class="json"> "development": { "username": "root", "password": null, "database": "yourdatabse", "host": "127.0.0.1", "port": "3308", "dialect": "mysql" }, </code></pre> <h2 id="実際にデータを読み書き"><a href="#%E5%AE%9F%E9%9A%9B%E3%81%AB%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E8%AA%AD%E3%81%BF%E6%9B%B8%E3%81%8D">実際にデータを読み書き</a></h2> <p>まずはSequelizeの設定を読み込んで使えるようにします。モデルなどは自動的に読み込んでくれるのでこの1文だけでOKです。</p> <pre><code>const models = require("../models"); </code></pre> <p><code>../models</code>は生成されたmodelsフォルダの位置です。階層が違う場合は適宜書き換えてください。</p> <p>ちなみにルーティングを別ファイルに分けている場合など、単にrequireしてもモデルがundefinedになっていることがありますので、その場合は最初にアプリケーションにmodelsを登録しておき、アクション内ではリクエストパラメータで取得すると良いです。</p> <pre><code class="javascript">app.set("models", require("../models")); </code></pre> <pre><code class="javascript">router.post("/posts", async function(req, res, next) { const Post = req.app.get("models").Post; </code></pre> <h3 id="書き込み"><a href="#%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF">書き込み</a></h3> <p>書き込み方法はいくつかあるので一例です。</p> <pre><code>models.User.build({ first_name: "Hoge", last_name: "Fuga" }) .save() </code></pre> <h3 id="読み込み"><a href="#%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF">読み込み</a></h3> <p>こちらも1例です。<code>plain: true</code>で取得すると単なるオブジェクトとして利用できるようになります。</p> <pre><code>router.get("/users", async function(req, res, next) { const users = await models.User.findAll(); res.json( users.map(user => { return user.get({ plain: true }); }) ); }); </code></pre> <p>例えばVueコンポーネント側で取得し</p> <pre><code class="javascript"> async asyncData() { let { users } = await axios.get("/api/users"); return { users }; } </code></pre> <p>受け取ったら下記のような感じで表示できます。上記の様にasyncDataで取っている場合はサーバーサイドレンダリングされています。</p> <pre><code class="html"> <div v-for="user in users" :key="user.id"> <span>{</span><span>{</span> user.name <span>}</span><span>}</span> </div> </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>とりあえずsequelize-cliがあることでかなり初期設定を短縮できています。これがなかったら特にはじめての人とかは結構つまづく箇所が多いのではないかと思います。</p> <p>他の言語の他のフレームワークと比べてもさほど使い勝手も変わらず良さそうな感じがしました。</p> <p>追記)TypeScriptで使うTypeORMも触ってみましたので書いておきます。<br /> <a href="https://crieit.net/posts/TypeORM-5ca0b3d5385b1">TypeORMを使ってアプリケーションを作ってみての雑感</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14501 2018-08-10T08:44:38+09:00 2019-10-29T12:57:08+09:00 https://crieit.net/posts/Nuxt-Express Nuxt+Expressのプロジェクト作成で良さそうなのは? <p>Nuxt.jsとExpressで新規プロジェクトを作成する方法をいくつか検証してみました。Nuxt.jsとExpressでアプリケーションを作成すると、SSR(サーバーサイドレンダリング)もデフォルトでやってくれるし、処理もサーバーサイドとフロント側で共通化できたりもして便利です。</p> <h2 id="どのような作成方法がある?"><a href="#%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AA%E4%BD%9C%E6%88%90%E6%96%B9%E6%B3%95%E3%81%8C%E3%81%82%E3%82%8B%EF%BC%9F">どのような作成方法がある?</a></h2> <p>プロジェクト自体はvue-cliで作成したりすることができます。</p> <h3 id="基本的な作成方法"><a href="#%E5%9F%BA%E6%9C%AC%E7%9A%84%E3%81%AA%E4%BD%9C%E6%88%90%E6%96%B9%E6%B3%95">基本的な作成方法</a></h3> <p>Nuxt.jsのマニュアルに紹介されているように、スターターテンプレートがあります。</p> <pre><code class="sh">vue init nuxt-community/starter-template <project-name> </code></pre> <p>これで最小限のNuxt.jsプロジェクトを作成することができますので、これにExpressを入れれば作成は可能です。ただ、もっと簡単にExpressを入れたプロジェクトを作成することができます。</p> <h3 id="Express入りのテンプレートを使う"><a href="#Express%E5%85%A5%E3%82%8A%E3%81%AE%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E4%BD%BF%E3%81%86">Express入りのテンプレートを使う</a></h3> <p>上記のnuxt-communityの他のリポジトリを見てみると、他にもいくつかテンプレートが用意されています。TypeScript入りのtypescript-template等。その中にexpress-templateもありますので、そちらを使ってプロジェクトを作成することができます。</p> <pre><code class="sh">vue init nuxt-community/express-template <project-name> </code></pre> <p>これならもっと簡単にNuxt.js+Expressのプロジェクトを作成することができます。</p> <p>ただ、これで試してみたところ、どうもExpress側のプログラムを更新してもホットリロードが行われないようで、その度にいちいちサーバーを再起動しなければなりません。ちょっと面倒だったので他のプロジェクト作成方法も探してみました。</p> <p>追記)現在はメンテナンスされておらず、使うべきでないものもあったりするようです。その都度確認してください。</p> <h3 id="create-nuxt-appを使う"><a href="#create-nuxt-app%E3%82%92%E4%BD%BF%E3%81%86">create-nuxt-appを使う</a></h3> <p>同じくnuxt-communityのリポジトリに、create-nuxt-appというものがありました。これはNuxt.jsのアプリケーションを作成する際に、構成を自分で選んで作成することができる非常に便利なものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/nuxt-community/create-nuxt-app">nuxt-community/create-nuxt-app: Create Nuxt.js App in seconds.</a></p> <p>下記のどちらかのコマンドで作成することができます。(僕はyarnの方は試していません)</p> <pre><code class="sh">npx create-nuxt-app <my-project> </code></pre> <pre><code class="sh">yarn create nuxt-app <my-project> </code></pre> <p>下記の構成を選択することができます。</p> <ul> <li>サーバーサイドのフレームワーク(Express等)</li> <li>UIフレームワーク(Bootstrap, Bulma等)</li> <li>axiosを使うか</li> <li>EsLintを使うか</li> </ul> <p>他にもissueを見るとTypeScriptを使うかも選択できるようにしたい、といった話も出ているので、実装されると嬉しいです。今後にも期待。</p> <p>これでExpressを選択するとExpressが動作するプロジェクトを作成することができます。しかもこちらはちゃんとNuxt.js側、Express側どちらのプログラムを修正してもちゃんとホットリロードしてくれるようになっています。(正確に言うと、package.jsonのスクリプトのnodemonで<code>--watch</code>を指定しているためです。監視すべきフォルダが複数あるのであれば何度でも<code>--watch</code>を使うことができます)</p> <p>ためしにexpress-templateに入っているユーザー情報取得処理をコピーしてみました。server/index.jsに下記のような処理を追加して、users.jsを読み込みます。ついでにルーティングも<code>/api</code>ベースにしておきます。</p> <pre><code class="javascript">const router = express.Router(); router.use(require("../routes/users")); app.use("/api", router); </code></pre> <p>Vueコンポーネント側でも同様に読み込みます。</p> <pre><code class="javascript"> async asyncData() { let { data } = await axios.get("/api/users"); return { users: data }; } </code></pre> <p>表示も同様です。</p> <pre><code class="html"><div v-for="user in users" :key="user.name"> <span>{</span><span>{</span> user.name <span>}</span><span>}</span> </div> </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回はとりあえずNuxt.js+Expressで簡単にプロジェクトを作成する触りの部分だけをやってみました。</p> <p>他にもMySQL接続やTwitter認証等、アプリケーションに必要なものを試しているので、よろしければそちらも御覧ください。</p> <p><a href="https://crieit.net/posts/Nuxt-js-Express-Twitter">Nuxt.js+Expressで簡単にTwitter認証</a></p> <p><a href="https://crieit.net/posts/Nuxt-js-Express-ORM">Nuxt.js+Expressでとにかく簡単にORM</a></p> <p><a href="https://crieit.net/posts/Nuxt-js-Express-TypeScript">Nuxt.js+ExpressのプロジェクトをTypeScript化する</a></p> だら@Crieit開発者