(import)nodejsで普通のCMSを作る

【2011年の投稿です】

旧ブログからインポート

HTML -> Markdownの変換には
http://domchristie.github.io/to-markdown/
があった。便利です。


node.jsといえば「リアルタイム系の何か」ということで、websocketでゲームとかは作っていたが、普通のWEBアプリも作ろうと思い。 そろそろブログも書かなければとも思い。 チュートリアルといえばblog tutorialなのでブログを作ってみた。

成果物はこのブログ。 ブログの基本的な機能は実装したつもり。 ただし、そもそも「ブログとは」がよく分からなかった。

コードはgithubに

環境です。

framework
express
templete engine
jade
ODM
mongoose

マークアップ

ブログとかのマークアップって変なHTML吐くだろーって思い込んで。結果、Haml風味の記法で普通にHTML書くようにした。よく使う表現はマクロっぽい機能で対応。 ※自分が変でないマークアップできるかは別の問題ですね

h3 hoge p hello div ul li a(href="piyo") fugafuga nagai bunnsyou 

みたいな感じで記述できる。

modules/parser.js で実装。

文章を沢山インデントするのは微妙と思い。タグだけインデントにしたけど、、 んんー、、矛盾があるのは仕方ない。

aaabbbccc だとaは自分で閉じるしか無い。風味風味

コードの表示

ブログに書く内容は主にプログラミング関係だと思うので、コードの表示はちゃんと実装しないと。エスケープはしてるから問題ないとして、表示。

SyntaxHighlighterを使いました。 結構種類があると思いますが、特に比較したわけではないですが、恐らくポピュラーで、対応言語も事足りそうだったので。他のも試してみたいですね。

SyntaxHighlighter

フレーズ機能

マクロといいうかただの置換も実装

{'#{code:(.*)}':'pre(class="brush:$1")'} 

のようなリストを使い正規表現の置換をする。絵文字とかもこれで登録しよう。

Tag機能

SQLでGROUP BYを使いたいようなとき、MongoDBではmap/reduceを使うのが正しいです。

タグはカンマ区切りで入力し、それを配列にして保存するようにした。

問題はタグの集計の方。サイドバーとかに出すやつ。と思ったら、丁度なのがあった! The MongoDB Cookbook > Counting Tags mongodbでのtagの集計が載っている。まずmongoで直接実行

map = function() { if (!this.tags) { return; } for (index in this.tags) { emit(this.tags[index], 1); } }; reduce = function(previous, current) { var count = 0; for (index in current) { count += current[index]; } return count; }; result = db.runCommand({ "mapreduce" : "articles", "map" : map, "reduce" : reduce, "out" : "tags"}); 

これをcronで実行するのが正しい気もするが、今回は記事を保存してすぐ反映させたい。そこで、node mongooseからrunCommandをする方法。接続がある状態で **executeDbCommand **を呼べばいいみたい。

 mongoose.connection.db.executeDbCommand(cmd, function(err,dbres){ console.log(err); console.log(dbres); }); 

こんなん。 深く考えずとりあえずモデルに実装してみた。

これをやってみて、mongodbをちゃんとやりたいと思った。

認証

twitterのOAuthを使うことにした。 Nodeでoauth使うには、oauthパッケージを使うのが楽。

実装は modules/auth.js

config

key/secretをハードコードした後、慌てて設定ファイルを作った。設定ファイルの実装に利用したのがconfigパッケージ。

  • 設定ファイルのディレクトリは/config* HOST名、NODE_ENVで設定ファイルを切り替えられる* yaml,json,jsが使える* 実行中にリロードできるruntime.jsonがある。

機能も十二分で。簡単にも使えて良いもの。

コメント

コメントはfacebookとtwitterのプラグインを使うことにした。

jadeにウィジェットとかコピペすんのめんどいね。 いい方法ないかな。

Trackback

「トラックバックとは?」状態だったけど、とりあえず受信だけ実装した。

次の4つをpostで受け取れば良いらしい。

  • title* excerpt* url* blog_name

トラックバック技術仕様書

この処理だけ、mongodbのupdate使った。

運用

nginxなどは使わずに、node単体で動かす。

forever

プロセスの管理にはforeverパッケージを使う。もし落ちても再起動してくれる。

NODE_ENV=production forever start app.js 

NODE_ENVを忘れずに

http-proxy

ポート分けの為のリバースプロキシもhttp-proxyパッケージを使うことで、nodeで簡単にできる。

var httpProxy = require('http-proxy'); var options = { hostnameOnly: true, router: { 'nakaji.me': '127.0.0.1:8000', 'articles.nakaji.me':'127.0.0.1:8001' } }; var proxyServer = httpProxy.createServer(options); proxyServer.listen(80); proxyServer.proxy.on('proxyError', function (err, req, res) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Something went wrong.'); }); 

v0.6

作っている途中で、Node v0.6がリリースされて、移行しようと思ったのだが、この2つのパッケージがv0.6で問題があり使えなかった。もうpull requestは上がってた。

バックアップ

データベースのバックアップにはmongodumpコマンドを使うのが一番楽そう。これを1日1回で

/usr/local/bin/mongodump -d article -o $NAME >> $LOG 

「node.jsが普通のWEBアプリ作るのに向いているのか?」を考えたが、blog作ってみた感想としては、とても楽でした。ストレスが少ない。javascriptが好きなだけかもしれないが。でかい規模になるとどうか分からないけど。node.jsで普通のWEBアプリいいですね。 サーバーサイドjs書いて、クライアントのjsも書いて、DBコマンドもjsで、いいですね。

勢いで書いたコードちゃんと直したら、READMEに使い方書きたい


"Nodeで普通のCMSを開発"の続き。 その後、いくつかバクを取ったり、ハードコーディングしていたものをちゃんとconfigファイルに移したりした。

マジックワードを極力なくしたので、他人が使おうと思えば使えるはず、ということでインストールしやすいようにpackage.jsonを書くことにした。

package.json

npmでのパッケージの依存関係などが定義できる。

作り方

npm init 

これで対話形式で基本的な項目の設定ができる。package.jsonができているのでこれを編集。

{ "author": "Daishi Nakajima (http://nakaji.me/)", "name": "articles", "description": "Articles is blog system. developed with node.js. database is used mongodb", "version": "0.3.0", "homepage": "https://github.com/nakaji-dayo/Articles", "repository": { "type": "git", "url": "git@github.com:nakaji-dayo/Articles.git" }, "engines": { "node": ">=v0.4.10" }, "dependencies": { "config" : "~0.4.6", "ejs" : "~0.5.0", "express" : "~2.5.1", "jade" : "~0.19.0", "mongoose" : "~2.4.1", "oauth" : "~0.9.5", "connect-mongodb":"~1.1.1" }, "devDependencies": {} } 

initで生成された後編集したのは、engines.nodeとdependencies。 チルダでのバージョン指定(~x)はx以上かつ下一桁のマイナーバージョンが変わらない範囲の意味らしい。

  • "~1.2.3" = ">=1.2.3 <1.3.0"* "~1.2" = ">=1.2.0 <2.0.0"* "~1" = ">=1.0.0 <2.0.0"

expressのsessionにmongoddbを使う

新しくしたののリリースを使用としていたときに

 Warning: connection.session() MemoryStore is not designed for a production environment, as it will leak memory, and obviously only work within a single process. 
なる警告が出ていたことに気づく!

調べてみるとredisを使う方法が多く出るが、折角mongodbが動いているのでこれを使いたい。

「connect-mongodb」というパッケージを使いました。connect-mongoじゃなくて、connect-mongodb

 app.use(express.cookieParser()); var mongoStore = require('connect-mongodb'); app.use(express.session({secret:config.session.secret, store: new mongoStore({url:config.db.connection}), cookie:{maxAge:config.session.age} })); 
こんな感じ

なんでそうしたのかとか合った気がするが、次のトラブルで放置している間に忘れた…

executeDbCommandのエラー

executeDbCommandでmongodbのコマンドを実行してタグの集計をしているのだが、いつからかこれが失敗している(結局いつからかは不明)。エラーは次のようなもの、

{ documents: [ { assertion: 'not code', assertionCode: 10062, errmsg: 'db assertion failure', ok: 0 } ], index: 129, messageLength: 129, requestId: 272, responseTo: 46, responseFlag: 8, cursorId: 0, startingFrom: 0, numberReturned: 1 } 
前のバージョンに戻したりしても治らなくて一旦放置。

その後調べなおして、結局渡す関数を文字列にしないといけないということだった。

 cmd = { "mapreduce" : "articles", "map" : (function() { if (!this.tags) { return; } for (index in this.tags) { emit(this.tags[index], 1); } }).toString()/これ/, "reduce" : (function(previous, current) { var count = 0; for (index in current) { count += current[index]; } return count; }).toString()/これ/, "out" : "tags"}; mongoose.connection.db.executeDbCommand(cmd, function(err,dbres){ console.log(err); console.log(dbres); }); 
なるほどね(しかし、ちゃんと動いていたはずなので釈然としない…)。