(import)nodejsで普通のCMSを作る
【2011年の投稿です】
旧ブログからインポート
HTML -> Markdownの変換には
http://domchristie.github.io/to-markdown/
があった。便利です。
node.jsといえば「リアルタイム系の何か」ということで、websocketでゲームとかは作っていたが、普通のWEBアプリも作ろうと思い。 そろそろブログも書かなければとも思い。 チュートリアルといえばblog tutorialなのでブログを作ってみた。
成果物はこのブログ。 ブログの基本的な機能は実装したつもり。 ただし、そもそも「ブログとは」がよく分からなかった。
環境です。
- framework
- express
- templete engine
- jade
- ODM
- mongoose
マークアップ
ブログとかのマークアップって変なHTML吐くだろーって思い込んで。結果、Haml風味の記法で普通にHTML書くようにした。よく使う表現はマクロっぽい機能で対応。 ※自分が変でないマークアップできるかは別の問題ですね
h3 hoge p hello div ul li a(href="piyo") fugafuga nagai bunnsyou
みたいな感じで記述できる。
文章を沢山インデントするのは微妙と思い。タグだけインデントにしたけど、、 んんー、、矛盾があるのは仕方ない。
aaabbbccc だとaは自分で閉じるしか無い。風味風味
コードの表示
ブログに書く内容は主にプログラミング関係だと思うので、コードの表示はちゃんと実装しないと。エスケープはしてるから問題ないとして、表示。
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); });なるほどね(しかし、ちゃんと動いていたはずなので釈然としない…)。