ExpressJSで完党なMVC Webサむトを構築する

NBこれは、node.jsの理論的基瀎に既に粟通しおおり、蚀うように、すぐにこのツヌルを䜿甚しお開発に突入したい人のための資料です。 控陀なし、コヌディングのみ。 興味があるなら、恥ずかしがらずに、猫の䞋を通り過ぎる。





翻蚳者から私自身、ごく最近node.jsを孊び始めたした。 間違いなく、むンタヌネット䞊には、初心者向けの倚くの優れたマニュアルがありたす特にここやハブなど。 しかし、著者Krasimir Tsonevのこのマニュアル 圌のマルチトピックブログを参照するこずもお勧めしたすは、MVCパタヌンずベヌスを䜿甚しおExpressモゞュヌルに基づいた本栌的なnode.jsアプリケヌションを構築するプロセスに぀いお説明しおいるため、特に泚目に倀するように思えたした。 MongoDBデヌタ。 著者はたた、テストなどの重芁なこずに泚意を払っおいたす。



翻蚳



この蚘事では、クラむアント郚分を備えた本栌的なWebサむトず、サむトのコンテンツ甚のコントロヌルパネルを構築したす。 ご想像のずおり、プログラムの最終バヌゞョンには倚数の異なるファむルが含たれおいたす。 このガむドを段階的に䜜成し、プロセスの開発を完党に監芖したしたが、すべおのファむルをその䞭に含めるこずは始めたせんでした。 ただし、゜ヌスコヌドはGitHubで入手できたす。チェックアりトするこずを匷くお勧めしたす。



゚ントリヌ



Expressは、最高のNodeフレヌムワヌクの1぀です。 優れた開発者サポヌトず倚数の䟿利な機胜を備えおいたす。 Expressには、それを扱うためのすべおの基本事項を網矅した玠晎らしい蚘事がたくさんありたす。 しかし、今はもう少し掘り䞋げお、本栌的なWebサむトを䜜成した経隓を共有したいず思いたす。 䞀般に、この蚘事はExpress自䜓に関するだけでなく、開発者が利甚できる他の劣らない玠晎らしいツヌルずの組み合わせに぀いおも説明しおいたす。

Node.jsに粟通し、システムにむンストヌルしたこず、およびその䞊にいく぀かのアプリケヌションを既に䜜成しおいるこずを信じおいたす。

ExpressフレヌムワヌクはConnectに基づいおいたす 。 これは、倚くの䟿利な機胜を備えたミドルりェア機胜のセットです。 ミドルりェア関数ずは䜕かに興味があるなら、ここに小さな䟋がありたす



var connect = require('connect'), http = require('http'); var app = connect() .use(function(req, res, next) { console.log("That's my first middleware"); next(); }) .use(function(req, res, next) { console.log("That's my second middleware"); next(); }) .use(function(req, res, next) { console.log("end"); res.end("hello world"); }); http.createServer(app).listen(3000);
      
      





ミドルりェア関数は、基本的に、リク゚ストオブゞェクトず応答オブゞェクトをパラメヌタヌずしお受け入れる関数であり、次に呌び出されるコヌルバック関数でもありたす。 各ミドルりェア関数は、応答オブゞェクトを䜿甚しお応答するか、ストリヌムを次のコヌルバック関数に枡すかを決定したす。 䞊蚘の䟋では、2番目の関数でnextメ゜ッド呌び出しを削陀するず、文字列「hello world」はクラむアントに送信されたせん。 䞀般的に、これがExpressの仕組みです。 それは間違いなくあなたの時間を節玄するいく぀かの定矩枈みのミドルりェア機胜を備えおいたす。 たずえば、リク゚ストボディを解析し、コンテンツタむプapplication / json 、 application / x-www-form-urlencodedおよびmultipart / form-dataをサポヌトするボディパヌサヌです。 たたは、 Cookieヘッダヌを解析し、キヌがCookieの名前であるオブゞェクトをreq.cookiesフィヌルドに入力するCookieパヌサヌ。

実際、Expressはそれ自䜓をConnectフレヌムワヌクでラップし、ルヌティングロゞックなどのいく぀かの機胜で補完しお、ルヌティングプロセスをスムヌズに進めたす。 以䞋は、GET芁求を凊理する䟋です。



 app.get('/hello.txt', function(req, res){ var body = 'Hello World'; res.setHeader('Content-Type', 'text/plain'); res.setHeader('Content-Length', body.length); res.end(body); });
      
      







蚭眮



Expressのむンストヌルには2぀のオプションがありたす。 1぀目は、その説明をpackage.jsonファむルに配眮しおnpm installコマンドを実行するこずですこのパッケヌゞマネヌゞャヌの名前は「no problem man」を衚しおいるようなゞョヌクがありたす:)。



 { "name": "MyWebSite", "description": "My website", "version": "0.0.1", "dependencies": { "express": "3.x" } }
      
      





フレヌムワヌクコヌドはnode_modulesディレクトリに配眮され、むンスタンスを䜜成できたす。 私は代替手段を奜みたすコマンドラむン。 npm install -g expressを実行しおExpressをむンストヌルするだけです。 これにより、新しいCLIツヌルが䜜成されたす。 たずえば、次の堎合



 express --sessions --css less --hogan app
      
      





Expressは、構成枈みのアプリケヌションフレヌムワヌクを生成したす。 以䞋は、 ゚クスプレス1コマンドオプションのリストです。



 Usage: express [options] Options: -h, --help output usage information -V, --version output the version number -s, --sessions add session support -e, --ejs add ejs engine support (defaults to jade) -J, --jshtml add jshtml engine support (defaults to jade) -H, --hogan add hogan.js engine support -c, --css add stylesheet support (less|stylus) (defaults to plain css) -f, --force force on non-empty directory
      
      





ご芧のずおり、䜿甚可胜なオプションのリストは小さいですが、これで十分です。 通垞、 lessラむブラリをCSSプリプロセッサずしお䜿甚し、 hoganテンプレヌト゚ンゞンも䜿甚したす。 この䟋では、セッションのサポヌトが必芁です。--sessionsオプションはこの問題を解決したす。 チヌムが完了するず、プロゞェクトの構造は次のようになりたす。



 /public /images /javascripts /stylesheets /routes /index.js /user.js /views /index.hjs /app.js /package.json
      
      





package.jsonファむルを芋るず、必芁なすべおの䟝存関係がここに远加されおいるこずがわかりたす。 しかし、ただむンストヌルされおいたせん。 これを行うには、npm installコマンドを実行したす 。その埌、 node_modulesディレクトリが衚瀺されたす 。

䞊蚘のアプロヌチは、ナニバヌサルず呌ぶに倀しないこずを理解しおいたす。 たずえば、ルヌトハンドラを別のディレクトリなどに配眮する必芁がある堎合がありたす。 しかし、次のいく぀かの章で瀺されるように、生成された構造に倉曎を加えたす。これは非垞に簡単です。 これに基づいお、 express1コマンドを単にテンプレヌトゞェネレヌタずしお䜿甚するこずをお勧めしたす。



高速配信



このチュヌトリアルでは、FastDeliveryずいう架空の䌚瀟の簡単なWebサむトを䜜成したした。 完成したデザむンのスクリヌンショットを次に瀺したす。





このガむドの最埌に、機胜するコントロヌルパネルを備えた既補のWebアプリケヌションがありたす。 アむデアは、サむトの各郚分を各郚分の別々の領域で管理する機胜を䜜成するこずです。 レむアりトはPhotoshopで䜜成され、CSSlessおよびHTMLhoganファむルにカットされたした。 ここではスラむスプロセスに぀いおは説明したせん。これは、この蚘事での議論の䞻題ではないためですが、この郚分に関しお質問がある堎合は、お気軜に質問しおください。 スラむスが終了するず、アプリケヌションファむルの次の構造が埗られたす。



 /public /images (there are several images exported from Photoshop) /javascripts /stylesheets /home.less /inner.less /style.css /style.less (imports home.less and inner.less) /routes /index.js /views /index.hjs (home page) /inner.hjs (template for every other page of the site) /app.js /package.json
      
      





以䞋に、管理できるサむト芁玠のリストを瀺したす。



構成



始める前にやらなければならないこずがいく぀かありたす。 構成のセットアップはそのようなものの1぀です。 小芏暡なサヌバヌが、ロヌカル、䞭間、戊闘の3぀の異なるサヌバヌに展開されるこずを想像しおみたしょう。 圓然、3぀の環境すべおの蚭定は異なるため、このような状況に十分に柔軟なメカニズムを実装する必芁がありたす。 ご存知のように、各nodejsスクリプトはコン゜ヌルアプリケヌションずしお実行されたす。 ぀たり、珟圚の環境を決定するコマンドにパラメヌタヌを簡単に割り圓おるこずができたす。 埌でテストを曞くのが䟿利になるように、この郚分を別のモゞュヌルにラップしたした。 /config/index.jsファむル



 var config = { local: { mode: 'local', port: 3000 }, staging: { mode: 'staging', port: 4000 }, production: { mode: 'production', port: 5000 } } module.exports = function(mode) { return config[mode || process.argv[2] || 'local'] || config.local; }
      
      





珟時点では、モヌドモヌドずポヌトポヌトの2぀のパラメヌタヌしかありたせん。 ご想像のずおり、アプリケヌションは異なるサヌバヌ䞊の異なるポヌトを䜿甚したす。 そのため、 app.jsファむルでサむトの゚ントリポむントを曎新する必芁がありたす。



 ... var config = require('./config')(); ... http.createServer(app).listen(config.port, function(){ console.log('Express server listening on port ' + config.port); });
      
      





構成を切り替えるには、環境の名前をコマンドの最初のパラメヌタヌに远加するだけです。 䟋



 node app.js staging
      
      





出力されたす



 Express server listening on port 4000
      
      





珟圚、すべおの蚭定は1か所に保存されおおり、管理が容易です。



テスト



私はTDDの倧ファンです。 この蚘事で取り䞊げるすべおの基本クラスをテストでカバヌするようにしたす。 絶察にすべおのテストを䜜成するのには倚くの時間がかかるこずに同意したすが、䞀般に、これがアプリケヌションの䜜成方法です。 私のお気に入りのテストフレヌムワヌクの1぀はゞャスミンです。 もちろん、 npmパッケヌゞマネヌゞャヌで利甚できたす。



 npm install -g jasmine-node
      
      





テストを保存するディレクトリを䜜成したしょう。 最初にテストするのは、構成ファむルです。 テストファむルの名前は.spec.jsで終わる必芁があるため、ファむルconfig.spec.jsを呌び出したす。



 describe("Configuration setup", function() { it("should load local configurations", function(next) { var config = require('../config')(); expect(config.mode).toBe('local'); next(); }); it("should load staging configurations", function(next) { var config = require('../config')('staging'); expect(config.mode).toBe('staging'); next(); }); it("should load production configurations", function(next) { var config = require('../config')('production'); expect(config.mode).toBe('production'); next(); }); });
      
      





jasmine-node ./testsコマンドを実行するず、次のように衚瀺されたす。



 Finished in 0.008 seconds 3 tests, 6 assertions, 0 failures, 0 skipped
      
      





今、私は最初に実装を曞き、次にテストを曞きたした。 これはTDDアプロヌチではありたせんが、次の章では反察のこずを行いたす。



テストを曞くのに十分な時間をかけるこずを匷くお勧めしたす。 完党にテストされたアプリケヌションに勝るものはありたせん。

数幎前、私は非垞に重芁な䜕か、より良いアプリケヌションを䜜成するのに圹立぀䜕かに気付きたした。 新しいクラス、新しいモゞュヌル、たたは単なるロゞックの䜜成を開始するたびに、次のこずを自問しおください。



これをどのようにテストできたすか


この質問に察する答えは、コヌドをより効率的に蚘述し、優れたAPIを䜜成し、すべおを適切に分離されたブロックに分割するのに圹立ちたす。 スパゲッティコヌドのテストは䜜成できたせん。 たずえば、䞊蚘の構成ファむル /config/index.js で、モゞュヌルコンストラクタヌにモヌドを送信する機胜を远加したした。 あなたは驚くかもしれたせん䞻なアむデアはコマンドラむン匕数からモヌドの倀を取埗するこずなので、なぜ私はこれをしたのですか 簡単です。テストする必芁があるからです。 1か月埌に戊闘サヌバヌの構成を䜿甚しお䜕かをテストする必芁があり、ノヌドスクリプトが代替パラメヌタヌで実行されるこずを想像しおみたしょう。 そしお、この小さな改善がなければ倉曎を加えるこずはできたせん。 この前の小さなステップは、将来のアプリケヌションの展開で起こりうる問題を防ぎたす。



デヌタベヌス



動的なWebサむトを構築する堎合、情報を保存するデヌタベヌスが必芁です。 このため、私はこの指瀺のためにmongodbを遞択したした 。 MongoはNoSQLデヌタベヌスです。 ここにむンストヌル手順がありたす 。私はWindowsナヌザヌなので、Windows のむンストヌル手順に埓いたした 。 むンストヌルが完了したら、デフォルトでポヌト27017をリッスンするMongoDBデヌモンを起動したす。したがっお、理論的には、このポヌトに接続しおmongodbサヌバヌずメッセヌゞを亀換する機䌚がありたす。 ノヌドスクリプトを介しおこれを行うには、 mongodb module / driverが必芁です 。 この手順の゜ヌスファむルをダりンロヌドした堎合、モゞュヌルは既にpackage.jsonファむルに远加されおいたす。 そうでない堎合は、 䟝存関係プロパティに「mongodb」「1.3.10」を远加し、 npm installコマンドを実行したす。 次に、mongodbサヌバヌが実行されおいるかどうかを確認するテストを䜜成したす。



/tests/mongodb.spec.jsファむル



 describe("MongoDB", function() { it("is there a server running", function(next) { var MongoClient = require('mongodb').MongoClient; MongoClient.connect('mongodb://127.0.0.1:27017/fastdelivery', function(err, db) { expect(err).toBe(null); next(); }); }); });
      
      





mongodbクラむアントメ゜ッド.connectのコヌルバック関数は、 dbオブゞェクトを受け取りたす。 埌でデヌタの管理に䜿甚したす。 これは、モデル内でアクセスする必芁があるこずを意味したす。 デヌタベヌスを照䌚するたびに新しいMongoClientオブゞェクトを䜜成するこずはお勧めできたせん。 そのため、Expressサヌバヌの起動をデヌタベヌスに接続するコヌルバック関数に転送したした。



 MongoClient.connect('mongodb://127.0.0.1:27017/fastdelivery', function(err, db) { if(err) { console.log('Sorry, there is no mongo db server running.'); } else { var attachDB = function(req, res, next) { req.db = db; next(); }; http.createServer(app).listen(config.port, function(){ console.log('Express server listening on port ' + config.port); }); } });
      
      





環境構成蚭定ファむルがあるため、mongodbサヌバヌのホスト名ずポヌトをここに配眮し、接続URLを次のように倉曎するこずをお勧めしたす。



'mongodb//' + config.mongo.host + '' + config.mongo.port + '/ fastdelivery'



http.createServer関数の呌び出しの盎前に远加したミドルりェア関数attachDBに特に泚意しおください 。 この小さな远加のおかげで、リク゚ストオブゞェクトの.dbプロパティを入力したす。 良いニュヌスは、ルヌト決定䞭にいく぀かの機胜を蚭定できるこずです。 䟋



 app.get('/', attachDB, function(req, res, next) { ... })
      
      





したがっお、ExpressはあらかじめattachDB関数を呌び出しお、ルヌトハンドラヌに入るようにしたす。 これが発生するず、リク゚ストオブゞェクトに.dbプロパティが蚭定され、それを䜿甚しおデヌタベヌスにアクセスできたす。



MVC



私たちはすべおMVCパタヌンに粟通しおいたす 。 問題は、Expressでそれを適甚する方法です。 いずれにせよ、それは解釈の問題です。 次のいく぀かの章では、モデル、ビュヌ、コントロヌラヌずしお機胜するモゞュヌルを䜜成したす。



モデル


モデルは、アプリケヌションのデヌタを凊理するものです。 MongoClientオブゞェクトによっお返されるdbオブゞェクトぞのアクセス暩が必芁です。 さたざたなタむプのモデルが必芁になる可胜性があるため、モデルにはそれを拡匵する方法も必芁です。 たずえば、 BlogModelたたはContactsModelモデルを䜜成する堎合がありたす。 これを行うには、最初にテスト/tests/base.model.spec.jsを䜜成する必芁がありたす。 そしお、これらのテストを䜜成するこずにより、モデルが必芁なこずだけを行い、それ以倖は䜕もしないこずを保蚌できたす。



 var Model = require("../models/Base"), dbMockup = {}; describe("Models", function() { it("should create a new model", function(next) { var model = new Model(dbMockup); expect(model.db).toBeDefined(); expect(model.extend).toBeDefined(); next(); }); it("should be extendable", function(next) { var model = new Model(dbMockup); var OtherTypeOfModel = model.extend({ myCustomModelMethod: function() { } }); var model2 = new OtherTypeOfModel(dbMockup); expect(model2.db).toBeDefined(); expect(model2.myCustomModelMethod).toBeDefined(); next(); }) });
      
      





実際のdbオブゞェクトの代わりに、モックオブゞェクトを枡すこずにしたした。 これは、埌で特定の䜕かをテストしたい堎合に行われたす。これは、デヌタベヌスからのデヌタに䟝存したす。 このデヌタを手動で定矩する方がはるかに簡単です。

プロトタむプmodule.exportsを倉曎する必芁がありたすが、元のコンストラクタヌはそのたたにしおおく必芁があるため、メ゜ッド拡匵の実装は少し難しいです。 幞いなこずに、コヌドのパフォヌマンスをチェックするテストをすでに䜜成しおいたす。 䞊蚘のコヌドスニペットのバヌゞョンは次のようになりたす。



 module.exports = function(db) { this.db = db; }; module.exports.prototype = { extend: function(properties) { var Child = module.exports; Child.prototype = module.exports.prototype; for(var key in properties) { Child.prototype[key] = properties[key]; } return Child; }, setDB: function(db) { this.db = db; }, collection: function() { if(this._collection) return this._collection; return this._collection = this.db.collection('fastdelivery-content'); } }
      
      





ここでは、2぀のヘルパヌメ゜ッドが定矩されおいたす。 デヌタベヌスからのコレクションのdbオブゞェクトずゲッタヌのセッタヌ。



提出


ビュヌは画面に情報を衚瀺したす。 簡単に蚀えば、ビュヌはブラりザヌに応答を送信するクラスです。 Expressは、これを行う簡単な方法を提䟛したす。



 res.render('index', { title: 'Express' });
      
      





応答オブゞェクトは、生掻を楜にする優れたAPIを備えたラッパヌです。 ただし、独自のモゞュヌルを䜜成しお、その機胜に組み蟌むこずを奜みたす。 ビュヌの暙準ディレクトリはテンプレヌトに眮き換えられ、ビュヌの叀いディレクトリはベヌスビュヌクラスを提䟛したす。

この小さな倉曎には、別の倉曎が必芁です。 テンプレヌトファむルが別のディレクトリにあるこずをExpressに通知する必芁がありたす。



 app.set('views', __dirname + '/templates');
      
      





たず、必芁なものをすべお決定し、テストを䜜成したす。その埌、実装を開始したす。 次の芁件を満たすモゞュヌルが必芁です。





驚くかもしれたせんなぜプレれンテヌションクラスを拡匵する必芁があったのですか 圌はresponse.renderメ゜ッドを呌び出すだけではありたせんか 実際、実際には、別のヘッダヌを送信したり、応答オブゞェクトを操䜜したりする必芁がある堎合がありたす。 たずえば、JSONで送信されたデヌタを凊理したす。



 var data = {"developer": "Krasimir Tsonev"}; response.contentType('application/json'); response.send(JSON.stringify(data));
      
      





これを毎回行う代わりに、 HTMLView クラスずJSONViewクラスを甚意するこずは玠晎らしいこずです。 たたは、XMLデヌタをブラりザに送信するXMLViewクラス。 同じコヌドを繰り返しコピヌしお貌り付けるよりも、そのような機胜が既に実装されおいる倧きなWebアプリケヌションを䜜成する方が簡単です。

以䞋は、 / views / Base.jsクラスのテストです。



 var View = require("../views/Base"); describe("Base view", function() { it("create and render new view", function(next) { var responseMockup = { render: function(template, data) { expect(data.myProperty).toBe('value'); expect(template).toBe('template-file'); next(); } } var v = new View(responseMockup, 'template-file'); v.render({myProperty: 'value'}); }); it("should be extendable", function(next) { var v = new View(); var OtherView = v.extend({ render: function(data) { expect(data.prop).toBe('yes'); next(); } }); var otherViewInstance = new OtherView(); expect(otherViewInstance.render).toBeDefined(); otherViewInstance.render({prop: 'yes'}); }); });
      
      





レンダリング芖芚化をテストするには、レむアりトを䜜成する必芁がありたした。 この堎合、テストの最初の郚分で、Expressフレヌムワヌクの応答オブゞェクトをシミュレヌトするオブゞェクトを䜜成したした。 テストの2番目の郚分では、基本クラスを継承し、独自のレンダリングメ゜ッドを䜿甚する別のViewクラスを䜜成したした。

クラスコヌド/views/Base.js 



 module.exports = function(response, template) { this.response = response; this.template = template; }; module.exports.prototype = { extend: function(properties) { var Child = module.exports; Child.prototype = module.exports.prototype; for(var key in properties) { Child.prototype[key] = properties[key]; } return Child; }, render: function(data) { if(this.response && this.template) { this.response.render(this.template, data); } } }
      
      





これで、テスト甚のディレクトリに3぀のテストがあり、 jasmine-node ./testsコマンドを実行するず、結果は次のようになりたす。



 Finished in 0.009 seconds 7 tests, 18 assertions, 0 failures, 0 skipped
      
      





コントロヌラヌ


ルヌトに぀いお話し、どのように決定したのか芚えおいたすか



 app.get('/', routes.index);
      
      





䞊蚘の䟋のルヌトの埌にある蚘号「/」はコントロヌラヌです。 これは、 リク゚スト 、 レスポンス 、コヌルバック関数nextを受け取るミドルりェア関数です。



 exports.index = function(req, res, next) { res.render('index', { title: 'Express' }); };
      
      





䞊蚘は、Expressを䜿甚する堎合のコントロヌラヌの倖芳を説明しおいたす。 express1コマンドは、 routesずいう名前でディレクトリを䜜成したすが、私たちの堎合は、 controllersず呌ばれる方が良いでしょう。 ファむル構造がこのパタヌンに䞀臎するように名前を倉曎したした。

単なる小さなアプリケヌション以䞊のものを構築しおいるため、拡匵可胜な基本クラスを䜜成するこずをお勧めしたす。 すべおのコントロヌラヌに機胜を远加する必芁がある堎合、この基本クラスはアドオンを実装するのに最適な堎所です。 繰り返したすが、私は最初にテストを曞いおいるので、基本クラスに必芁なものを定矩したしょう



ほんの数点ですが、埌で機胜を远加するこずができたす。

テストは次のようになりたす。



 var BaseController = require("../controllers/Base"); describe("Base controller", function() { it("should have a method extend which returns a child instance", function(next) { expect(BaseController.extend).toBeDefined(); var child = BaseController.extend({ name: "my child controller" }); expect(child.run).toBeDefined(); expect(child.name).toBe("my child controller"); next(); }); it("should be able to create different childs", function(next) { var childA = BaseController.extend({ name: "child A", customProperty: 'value' }); var childB = BaseController.extend({ name: "child B" }); expect(childA.name).not.toBe(childB.name); expect(childB.customProperty).not.toBeDefined(); next(); }); });
      
      





以䞋は/controllers/Base.jsの実装です



 var _ = require("underscore"); module.exports = { name: "base", extend: function(child) { return _.extend({}, this, child); }, run: function(req, res, next) { } }
      
      





もちろん、各子孫クラスには、独自のロゞックを実装する独自のrunメ゜ッドが必芁です。



FastDelivery Webサむト



たあ、MVCアヌキテクチャに適したクラスのセットがあり、すべおのモゞュヌルをテストでカバヌしたした。 これで、架空の䌚瀟FastDeliveryのサむトを匕き続き扱う準備ができたした。 サむトが2぀の郚分ナヌザヌ郚分ず管理パネルで構成されるこずを想像しおみたしょう。 ナヌザヌ郚分は、デヌタベヌスに保存されおいる情報を゚ンドナヌザヌに提瀺するためのものです。管理パネルは、この情報を管理するためのものです。最埌から始めたしょう。



制埡盀


最初に、管理パネルペヌゞを提䟛するシンプルなコントロヌラヌを䜜成したしょう。

ファむル/controllers/Admin.js



 var BaseController = require("./Base"), View = require("../views/Base"); module.exports = BaseController.extend({ name: "Admin", run: function(req, res, next) { var v = new View(res, 'admin'); v.render({ title: 'Administration', content: 'Welcome to the control panel' }); } });
      
      





コントロヌラヌずビュヌの事前に䜜成された基本クラスを䜿甚しお、管理パネルの゚ントリポむントを簡単に䜜成できたす。Viewクラスは、テンプレヌトファむルの名前を受け入れたす。䞊蚘のコヌドによるず、ファむルはadmin.hjsずいう名前で、/ templatesディレクトリに配眮する必芁がありたす。その内容は次のようになりたす。



 <!DOCTYPE html> <html> <head> <title>{{ title }}</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <div class="container"> <h1>{{ content }}</h1> </div> </body> </html>
      
      





この指瀺を短く読みやすくするために、ここでは各テンプレヌトのコヌドを提䟛したせん。GitHubから゜ヌスコヌドをダりンロヌドするこずを匷くお勧めしたす。

次に、コントロヌラヌを衚瀺するために、アプリにルヌトを远加する必芁がありたす。.js



 var Admin = require('./controllers/Admin'); ... var attachDB = function(req, res, next) { req.db = db; next(); }; ... app.all('/admin*', attachDB, function(req, res, next) { Admin.run(req, res, next); });
      
      





Admin.runメ゜ッドをミドルりェア関数ずしお盎接送信しないこずに泚意しおください。これは、コンテキストを保持するためです。これを行う堎合



 app.all('/admin*', Admin.run);
      
      





Adminオブゞェクトのthisずいう単語は、たったく別の堎所を指したす。



管理パネルの保護


/ adminで始たる各ペヌゞは保護する必芁がありたす。これを実珟するために、組み蟌みのExpressミドルりェア機胜であるSessionsを䜿甚したす。セッションず呌ばれるリク゚ストにオブゞェクトを远加するだけです。次の2぀のこずを行うように、管理コントロヌラヌを倉曎する必芁がありたす。



以䞋は、䞊蚘の芁件を満たす小さなヘルパヌ関数です。



 authorize: function(req) { return ( req.session && req.session.fastdelivery && req.session.fastdelivery === true ) || ( req.body && req.body.username === this.username && req.body.password === this.password ); }
      
      





最初に、セッションオブゞェクトを介しおナヌザヌを認識しようずするスクリプトが実行されたす。次に、フォヌムからのデヌタが送信されたかどうかを確認したす。その堎合、ミドルりェア関数bodyParserが甚意したrequest.bodyオブゞェクトからフォヌムのデヌタを利甚できたす。次に、ナヌザヌ名ずパスワヌドが䞀臎するかどうかを確認したす。そしお、コントロヌラヌからrunメ゜ッドを実行するずきが来たした。このメ゜ッドは、新しく䜜成されたヘルパヌすべおのコントロヌラヌの基本ラッパヌを䜿甚したす。チェックナヌザヌが承認されおいる堎合、コントロヌルパネルを衚瀺し、そうでない堎合は承認ペヌゞを衚瀺したす。





 run: function(req, res, next) { if(this.authorize(req)) { req.session.fastdelivery = true; req.session.save(function(err) { var v = new View(res, 'admin'); v.render({ title: 'Administration', content: 'Welcome to the control panel' }); }); } else { var v = new View(res, 'admin-login'); v.render({ title: 'Please login' }); } }
      
      







コンテンツ管理


この蚘事の冒頭で述べたように、管理するものがたくさんありたす。プロセスを簡玠化するために、1぀のコレクションにすべおのデヌタを保持したしょう。各゚ントリには、タむトル、テキスト、画像、およびタむプのプロパティがありたす。埌者がレコヌドの所有者を決定したす。たずえば、連絡先ペヌゞには「連絡先」タむプの゚ントリが1぀だけ必芁ですが、ブログペヌゞにはさらに倚くの゚ントリが必芁です。そのため、゚ントリを远加、削陀、線集するには、3぀の新しいペヌゞを远加する必芁がありたす。新しいテンプレヌトの䜜成、スタむリング、およびコントロヌラヌぞの新しいものの远加を開始する前に、MongoDBサヌバヌずアプリケヌションの間にあるモデルのクラスを䜜成する必芁がありたす。もちろん、䟿利なAPIを提䟛したす。



 // /models/ContentModel.js var Model = require("./Base"), crypto = require("crypto"), model = new Model(); var ContentModel = model.extend({ insert: function(data, callback) { data.ID = crypto.randomBytes(20).toString('hex'); this.collection().insert(data, {}, callback || function(){ }); }, update: function(data, callback) { this.collection().update({ID: data.ID}, data, {}, callback || function(){ }); }, getlist: function(callback, query) { this.collection().find(query || {}).toArray(callback); }, remove: function(ID, callback) { this.collection().findAndModify({ID: ID}, [], {}, {remove: true}, callback); } }); module.exports = ContentModel;
      
      





モデルは、各レコヌドに䞀意のIDを生成したす。埌で情報を曎新するために必芁になりたす。

連絡先ペヌゞに新しい゚ントリを远加する必芁がある堎合、次のように蚘述できたす。



 var model = new (require("../models/ContentModel")); model.insert({ title: "Contacts", text: "...", type: "contacts" });
      
      





そのため、mongodbコレクションには非垞に優れたデヌタ管理APIがありたす。これで、このすべおの機胜を䜿甚するUIを䜜成する準備ができたした。今回は、管理コントロヌラヌがかなり倉曎されたす。タスクを簡玠化するために、远加したレコヌドのリストず、レコヌドを远加/線集するためのフォヌムを組み合わせるこずにしたした。䞋のスクリヌンショットでわかるように、ペヌゞの巊偎はリスト甚に、右偎はフォヌム甚に予玄されおいたす。



すべおのコントロヌルを1ペヌゞに衚瀺するずいうこずは、このペヌゞをレンダリングする郚分に焊点を合わせる必芁があるこずを意味したす。具䜓的には、テンプレヌトに送信するデヌタに焊点を合わせる必芁がありたす。したがっお、次のようないく぀かの統合されたヘルパヌ関数を䜜成したした。



 var self = this; ... var v = new View(res, 'admin'); self.del(req, function() { self.form(req, res, function(formMarkup) { self.list(function(listMarkup) { v.render({ title: 'Administration', content: 'Welcome to the control panel', list: listMarkup, form: formMarkup }); }); }); });
      
      





はい、それは少しlooksいですが、それは私が望むように正確に動䜜したす。最初のヘルパヌはdelメ゜ッドで、珟圚のGETパラメヌタヌをチェックし、action = deleteid = [レコヌドのID]の組み合わせを芋぀けた堎合、察応するデヌタをコレクションから削陀したす。2番目の関数はformず呌ばれ、䞻にペヌゞの右偎にフォヌムを衚瀺したす。この関数は、フォヌムがサヌバヌに到着したかどうかを確認し、デヌタベヌス内のレコヌドを適切に曎新たたは䜜成したす。最埌に、listメ゜ッドは情報を取埗し、埌でテンプレヌトに送信されるHTMLテヌブルを準備したす。これら3぀のヘルパヌの実装は、この呜什の゜ヌスコヌドにありたす。

以䞋は、ファむルのアップロヌドを凊理する関数です。



 handleFileUpload: function(req) { if(!req.files || !req.files.picture || !req.files.picture.name) { return req.body.currentPicture || ''; } var data = fs.readFileSync(req.files.picture.path); var fileName = req.files.picture.name; var uid = crypto.randomBytes(10).toString('hex'); var dir = __dirname + "/../public/uploads/" + uid; fs.mkdirSync(dir, '0777'); fs.writeFileSync(dir + "/" + fileName, data); return '/uploads/' + uid + "/" + fileName; }
      
      





ファむルがサヌバヌに送信されるず、芁求オブゞェクトの.filesプロパティに察応するデヌタが入力されたす。この堎合、次のHTML芁玠がありたす。



 <input type="file" name="picture" />
      
      





これは、req.files.pictureを介しお送信されたデヌタにアクセスできるこずを意味したす。䞊蚘の䟋に瀺すように、req.files.picture.pathを䜿甚しお、ファむルの未加工未加工コンテンツを取埗したす。さらに、送信されたファむルはダりンロヌド甚に䜜成されたディレクトリに保存され、最埌に、このファむルに察応するURLが返されたす。これらの操䜜はすべお同期ですが、非同期バヌゞョンのreadFileSync、mkdirSync、およびwriteFileSyncメ゜ッドを䜿甚するこずをお勧めしたす。



ナヌザヌ郚


最も難しい郚分の準備ができたした。管理パネルが動䜜し、デヌタベヌスに保存されおいる情報にアクセスできるContentModelクラスがありたす。ここで必芁なのは、ナヌザヌ郚分のコントロヌラヌを䜜成し、保存されたコンテンツにバむンドするこずです。これがホヌムペヌゞのコントロヌラヌです- /controllers/Home.js



 module.exports = BaseController.extend({ name: "Home", content: null, run: function(req, res, next) { model.setDB(req.db); var self = this; this.getContent(function() { var v = new View(res, 'home'); v.render(self.content); }) }, getContent: function(callback) { var self = this; this.content = {}; model.getlist(function(err, records) { ... storing data to content object model.getlist(function(err, records) { ... storing data to content object callback(); }, { type: 'blog' }); }, { type: 'home' }); } });
      
      





ホヌムペヌゞには、タむプが「home」の1぀の゚ントリず、タむプが「blog」の4぀の゚ントリが必芁です。

コントロヌラヌの準備ができたら、app.jsでコントロヌラヌぞのルヌトを远加する必芁がありたす。



 app.all('/', attachDB, function(req, res, next) { Home.run(req, res, next); });
      
      





再床dbオブゞェクトをリク゚ストオブゞェクトにアタッチしたす。ほずんどの堎合、管理パネルの䜜成時に実行したプロセスず同じです。

ナヌザヌパヌツの他のペヌゞは、モデルクラスを䜿甚しおデヌタを受信するコントロヌラヌがあり、もちろんルヌトがあるずいう点で、ほずんど同じです。さらに詳しく説明したい2぀の興味深い偎面がありたす。最初はブログペヌゞにリンクされおいたす。このペヌゞには、すべおの蚘事ず1぀の蚘事が衚瀺されたす。ルヌトのペアを定矩したす。



 app.all('/blog/:id', attachDB, function(req, res, next) { Blog.runArticle(req, res, next); }); app.all('/blog', attachDB, function(req, res, next) { Blog.run(req, res, next); });
      
      





䞡方ずも同じコントロヌラヌBlogを䜿甚したすが、2぀の異なるrunメ゜ッドを呌び出したす。行/ blog /idに泚意しおください。このルヌトは、/ blog / 4e3455635b4a6f6dccfaa1e50ee71f1cde75222bなどのURLに䞀臎したす。この長いハッシュには、req.params.idプロパティからアクセスできたす。぀たり、動的パラメヌタヌを定矩できたす。私たちの堎合、これぱントリ蚘事のIDになりたす。これに぀いお孊習したので、蚘事ごずに䞀意のペヌゞを生成できたす。

2番目の興味深い偎面は、サヌビス、キャリア、連絡先のペヌゞをどのように䜜成するかです。それらのすべおがデヌタベヌスの1぀のレコヌドのみを䜿甚するこずは明らかです。各ペヌゞにコントロヌラヌを䜜成する必芁がある堎合は、同じコヌドをコピヌしお貌り付け、その䞭のtypeフィヌルドを倉曎するだけです。runメ゜ッドでレコヌドタむプを受け入れる単䞀のコントロヌラヌを䜿甚しお、これを行うより良い方法がありたす。ルヌトは次のずおりです。



 app.all('/services', attachDB, function(req, res, next) { Page.run('services', req, res, next); }); app.all('/careers', attachDB, function(req, res, next) { Page.run('careers', req, res, next); }); app.all('/contacts', attachDB, function(req, res, next) { Page.run('contacts', req, res, next); });
      
      





そしお、コントロヌラヌは次のようになりたす。



 module.exports = BaseController.extend({ name: "Page", content: null, run: function(type, req, res, next) { model.setDB(req.db); var self = this; this.getContent(type, function() { var v = new View(res, 'inner'); v.render(self.content); }); }, getContent: function(type, callback) { var self = this; this.content = {} model.getlist(function(err, records) { if(records.length > 0) { self.content = records[0]; } callback(); }, { type: type }); } });
      
      







展開



Express Webサむトをデプロむするこずは、Node.jsに他のアプリケヌションをデプロむするこずず本質的に違いはありたせん。



Nodeは非垞に新しい補品であり、すべおが期埅どおりに機胜するわけではありたせんが、垞に改善が行われおいるこずに泚意しおください。たずえば、foreverモゞュヌルはNode.jsの継続的な動䜜を保蚌したす。次のコマンドを実行しお実行できたす。



 forever start yourapp.js
      
      





これはサヌバヌで䜿甚するものです。この小さなツヌルは、1぀の倧きな問題を解決したす。node yourapp.jsコマンドを䜿甚しおアプリケヌションを起動するず、スクリプトが倱敗するずすぐにサヌバヌがクラッシュしたすが、氞久にアプリケヌションを再起動するだけです。

私はシステム管理者ではありたせんが、ApacheずNginxでノヌドアプリケヌションを統合した経隓を共有したいず思いたす。統合は開発プロセスの䞀郚だず考えおいるからです。ご存じのように、Apacheは通垞ポヌト80で実行されたす。぀たり、localhostたたはlocalhostのブラりザヌにアクセスするず80、Apacheサヌバヌが提䟛するペヌゞが衚瀺されたす。おそらく、ノヌドスクリプトは別のポヌトでリッスンしおいたす。したがっお、芁求を受信しお​​目的のポヌトに送信する仮想ホストを远加する必芁がありたす。たずえば、アドレスexpresscompletewebsite.devの䞋で、ロヌカルのApacheサヌバヌで䜜成したばかりのサむトをホストする必芁がありたす。最初に行う必芁があるのは、ホストファむルにドメむンを远加するこずです。



 127.0.0.1 expresscompletewebsite.dev
      
      





その埌、Apacheサヌバヌの構成ディレクトリにあるhttpd-vhosts.confファむルを倉曎する必芁がありたす。



 # expresscompletewebsite.dev <VirtualHost *:80> ServerName expresscompletewebsite.dev ServerAlias www.expresscompletewebsite.dev ProxyRequests off <Proxy *> Order deny,allow Allow from all </Proxy> <Location /> ProxyPass http://localhost:3000/ ProxyPassReverse http://localhost:3000/ </Location> </VirtualHost>
      
      







サヌバヌはポヌト80からの芁求を受け入れたすが、ノヌドスクリプトをリッスンするポヌト3000に芁求を転送したす。

Nginxの構成ははるかに簡単で、率盎に蚀っお、Node.jsベヌスのアプリケヌションをホストするための最良の遞択です。 Apache構成ず同様に、hostsファむルにドメむンを远加する必芁がありたす。その埌、Nginxがむンストヌルされおいる堎所にある/ sites-enabledディレクトリに新しいファむルを䜜成したす。ファむルの内容は次のようになりたす。



 server { listen 80; server_name expresscompletewebsite.dev location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $http_host; } }
      
      







同じホスト構成でApacheずNginxを同時に起動するこずはできないこずに泚意しおください。これは、どちらもポヌト80でリッスンするためです。さらに、戊闘サヌバヌで䞊蚘のコヌドスニペットを䜿甚する堎合は、むンタヌネットでより詳现なサヌバヌ構成情報を怜玢できたす。私が蚀ったように、私はこの分野の専門家ではありたせん。



おわりに



Expressは、Webアプリケヌションを構築するための優れた基盀を提䟛する優れたフレヌムワヌクです。あなた自身が芋るこずができるように、それは遞択の問題、それをどのように拡匵するか、そしおそれで䜕を䜿うかだけです。いく぀かの玠晎らしいミドルりェアを䜿甚しおルヌチンタスクを簡玠化し、開発者にずっお最もおいしいものを実装するプロセスを残したす。



゜ヌスコヌド


GitHubのこの蚘事で䜜成したWebサむトの゜ヌスコヌドはhttps://github.com/tutsplus/build-complete-website-expressjsです。分岐しおアップグレヌドしおください。りェブサむトを立ち䞊げるための段階的なガむドは次のずおりです。






All Articles