自転車を発明した方法、または最初のMEANプロジェクト







今日、Webテクノロジーの急速な開発期間中、経験豊富なフロントエンド開発者は常にトレンドにとどまり、知識を日々深めていく必要があります。 しかし、ウェブの世界で旅を始めたばかりの場合はどうでしょうか? 既にレイアウトに問題があり、そこで停止したくない場合。 JavaScriptの神秘的な世界に引き付けられます! これがあなたのことなら、この記事が役に立つことを願っています。







フロントエンド開発者として1年半の経験があり、私は別の通常のプロジェクトの単調なレイアウトにうんざりして、Webプログラミングの分野での知識を深め始めました。 最初の単一ページアプリケーションを作成したいと思っていました。 テクノロジースタックの選択は明らかでした。Node.jsに私はいつも無関心ではなかったため、MEAN方法論は医師が処方したものになりました。







今日、インターネットには、helloworld、todo、管理機関などのアプリケーションを作成するさまざまなチュートリアルがあります。 ただし、チュートリアルの手順を無意識に実行することは、私の選択ではありません。 ある種のメッセンジャーを作成することにしました。新しいユーザーの登録、ユーザー間のダイアログの作成、テストユーザー用のチャットボットとの通信が可能なアプリケーションです。 それで、行動計画を慎重に考え出して、仕事に取り掛かりました。







さらに、私のストーリーでは、このアプリケーションを作成する主なポイントについて説明します。明確にするために、ここではデモを残しますgithubへのリンク )。







*また、この記事の目的は、おそらく、私が一度踏み込んだ熊手を踏まないようにすることと、経験豊富な開発者がコードを表示してコメントで意見を表明できるようにすることです。







アクションプランを作成しましょう。







  1. 準備作業
  2. 認可システムの作成
  3. Angular2とSocket.ioでチャットする


準備作業



職場の準備はあらゆる開発の不可欠なプロセスであり、このタスクの質の高い実装は将来の成功の鍵です。 まず、Expressをインストールし、プロジェクト用に単一の構成システムを構成する必要があります。 最初のものですべてが明確な場合は、2番目の詳細について詳しく説明します。







そして、素晴らしいnconfモジュールを使用します。 configというフォルダーを作成し、そのインデックスファイルに書き込みましょう。







const nconf = require('nconf'); const path = require('path'); nconf.argv() .env() .file({ file: path.join(__dirname, './config.json') }); module.exports = nconf;
      
      





次に、このフォルダーでconfig.jsonというファイルを作成し、その中に最初の設定(アプリケーションがリッスンするポート)を作成します。







 { "port": 2016 }
      
      





この設定をアプリケーションに実装するには、1行/ 2行のコードを書くだけです。







 const config = require('./config'); let port = process.env.PORT || config.get('port'); app.set('port', port);
      
      





ただし、ポートが次のように指定されている場合、これは機能することに注意してください。







 const server = http.createServer(app); server.listen(app.get('port'));
      
      





次のタスクは、アプリケーションで単一のロギングシステムを構成することです。 「Node.jsのロギングについてという記事の著者として次のように書いています。







ログにたくさん書く必要があります。 アプリケーションの現在の状態を理解することはほとんどなく、アプリケーションがクラッシュした場合にその理由を理解することもできます。

このタスクには、 winstonモジュールを使用します。







 const winston = require('winston'); const env = process.env.NODE_ENV; function getLogger(module) { let path = module.filename.split('\\').slice(-2).join('/'); return new winston.Logger({ transports: [ new winston.transports.Console({ level: env == 'development' ? 'debug' : 'error', showLevel: true, colorize: true, label: path }) ] }); } module.exports = getLogger;
      
      





もちろん、設定はもっと柔軟かもしれませんが、この段階ではこれで十分です。 新しく作成したロガーを使用するには、このモジュールを作業ファイルに接続し、適切な場所で呼び出すだけです。







 const log = require('./libs/log')(module); log.info('Have a nice day =)');
      
      





次のタスクは、通常のリクエストとajaxリクエストの正しいエラー処理を設定することです。 これを行うには、Expressが以前に生成したコードにいくつかの変更を加えます(この例では、開発エラーハンドラーのみが指定されています)。







 // development error handler if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); if(res.req.headers['x-requested-with'] == 'XMLHttpRequest'){ res.json(err); } else{ // will print stacktrace res.render('error', { message: err.message, error: err }); } }); }
      
      





準備作業はほぼ完了しましたが、小さな作業が1つ残っていますが、決して重要ではない詳細はありません。データベースで作業をセットアップすることです。 最初に、 mongooseモジュールを使用してMongoDBへの接続を構成します。







 const mongoose = require('mongoose'); const config = require('../config'); mongoose.connect(config.get('mongoose:uri'), config.get('mongoose:options')); module.exports = mongoose;
      
      





mongoose.connectでは、 2つの引数urioptionsを渡します 。これらは事前に構成で登録しました(これらの詳細については、モジュールのドキュメントを参照してください )。







Webリソースlearn.javascript.ruの作成者は、ビデオチュートリアル「 ユーザーのモデルの作成/ Mongooseの基本 」でNode.jsのスクリーンキャストで同様の方法でプロセスを説明しているため、ユーザーモデルとダイアログの作成プロセスについては説明しません。ユーザーには、ユーザー名、hashedPassword、salt、ダイアログなどのプロパティがあり、作成されます。 次に、dialogsプロパティはオブジェクトを返します:key-対話者のid、value-ダイアログのid。







誰かがまだこれらのモデルのコードを見ることに興味があるなら:







users.js
 const mongoose = require('../libs/mongoose'); const Schema = mongoose.Schema; const crypto = require('crypto'); let userSchema = new Schema({ username: { type: String, unique: true, required: true }, hashedPassword: { type: String, required: true }, salt: { type: String, required: true }, dialogs: { type: Schema.Types.Mixed, default: {defaulteDialog: 1} }, created: { type: Date, default: Date.now } }); userSchema.methods.encryptPassword = function(password){ return crypto.createHmac('sha1', this.salt).update(password).digest('hex'); }; userSchema.methods.checkPassword = function(password){ return this.encryptPassword(password) === this.hashedPassword; } userSchema.virtual('password') .set(function(password){ this._plainPassword = password; this.salt = Math.random() + ''; this.hashedPassword = this.encryptPassword(password); }) .get(function(){ return this._plainPassword; }); module.exports = mongoose.model('User', userSchema);
      
      



dialogs.js
 const mongoose = require('../libs/mongoose'); const Schema = mongoose.Schema; let dialogSchema = new Schema({ data: { type: [], required: true } }) module.exports = mongoose.model('Dialog', dialogSchema);
      
      





残っているのは、セッションをアプリケーションのバックボーンにねじ込むことだけです。 これを行うには、session.jsファイルを作成し、 express-sessionconnect-mongoなどのモジュール、およびmongoose.jsファイルから作成したモジュールをプラグインします。







 const mongoose = require('./mongoose'); const session = require('express-session'); const MongoStore = require('connect-mongo')(session); module.exports = session({ secret: 'My secret key!', resave: false, saveUninitialized: true, cookie:{ maxAge: null, httpOnly: true, path: '/' }, store: new MongoStore({mongooseConnection: mongoose.connection}) })
      
      





この設定を別のファイルに置くことは重要ですが、必須ではありません。 これにより、セッションとWebソケットをそれらの間でさらに困難なく調和させることができます。 次に、app.jsでこのモジュールを接続します。







 const session = require('./libs/session'); app.use(session);
      
      





さらに、 app.use( cookieParser())の後にapp.use(セッション)を指定する必要があります。これにより、Cookieがすでに読み取られます。 それだけです! これで、セッションをデータベースに保存できるようになりました。







これで準備作業は終わりました。 楽しい部分を始めましょう!







認証システムの作成



承認システムの作成は、フロントエンドとバックエンドの2つの主要な段階に分けられます。 このアプリケーションを開始してから、私は常に新しいことを学ぼうとしていたので、Angular1.xですでに経験を積んでいたので、Angular2でフロントエンド部分を整理することにしました。 アプリケーションを作成したとき、このフレームワークの4番目(そして5番目)のプレリリースバージョンが既にリリースされていたという事実は、オフリリースが間近に迫っていることに自信を与えました。 そして、私の考えを集めて、承認を書くために座った。







Angular2での開発にまだ遭遇していない人にとっては、以下のコードであなたが知らないjavascript構文を見つけても驚かないでください。 問題は、Angular2のすべてがtypescript上に構築されていることです。 いいえ、これは通常のJavaScriptを使用してこのフレームワークを操作することが不可能であることを意味しません! たとえば、素晴らしい記事があります。その間、著者はES6を使用してAngular2での開発を検討しています。







しかし、typescriptはスケーリングするjavascriptです。 コンパイルされたjavascriptのスーパーセットであるこの言語は、ES6およびES7からのすべての機能、ブラックジャックとクラスを使用した実際のOOP、強力な型指定、その他多くのクールな機能を追加します。 そして、恐れることは何もありません。結局のところ、javascriptで有効なものはすべてtypescriptでも機能します。







まず、user-authenticate.service.tsファイルを作成します。このファイルには承認サービスが含まれます。







 import { Injectable } from '@angular/core'; import { Http, Headers } from '@angular/http'; @Injectable() export class UserAuthenticateService{ private authenticated = false; constructor(private http: Http) {} }
      
      





次に、クラス内で、いくつかのメソッドを作成します:login、logout、singup、isLoggedIn。 これらのメソッドはすべて同じタイプです。それぞれがタイプpostのリクエストを適切なアドレスに送信するタスクを実行します。 それぞれの論理的な負荷を推測することは難しくありません。 ログイン方法のコードを検討してください。







 login(username, password) { let self = this; let headers = new Headers(); headers.append('Content-Type', 'application/json'); return this.http .post( 'authentication/login', JSON.stringify({ username, password }), { headers }) .map(function(res){ let answer = res.json(); self.authenticated = answer.authenticated; return answer; }); }
      
      





Angular2コンポーネントからこのメソッドを呼び出すには、対応するコンポーネントにこのサービスを実装する必要があります。







 import { UserAuthenticateService } from '../services/user-authenticate.service'; @Component({ ... }) export class SingInComponent{ constructor(private userAuthenticateService: UserAuthenticateService, private router: Router){ ... } onSubmit() { let self = this; let username = this.form.name.value; let password = this.form.password.value; this.userAuthenticateService .login(username, password) .subscribe(function(result) { self.onSubmitResult(result); }); } }
      
      





異なるコンポーネントからサービスの同じインスタンスにアクセスするには、共通の親コンポーネントに実装する必要があります。







そしてこれで、承認システムを作成するフロントエンド段階を終了します。







バックエンドの開発を始めるには、興味深い非同期モジュール(モジュールのドキュメント )をよく理解することをお勧めします。 非同期JavaScript関数を操作するための強力なツールとなります。







既存のルートディレクトリにauthentication.jsファイルを作成しましょう。 次に、app.jsでこのミドルウェアを示します。







 const authentication = require('./routes/authentication'); app.use('/authentication', authentication);
      
      





次に、認証/ログインアドレスへの投稿を要求するハンドラーを作成します。 さまざまなif ... elseから長いシートを作成しないように、上記の非同期モジュールのwaterfallメソッドを使用します。 このメソッドを使用すると、非同期タスクのコレクションを順番に実行し、前のタスクの結果を次のタスクの引数に渡し、出力に対して有用なコールバックを実行できます。 このコールバックを書きましょう:







 const express = require('express'); const router = express.Router(); const User = require('../models/users'); const Response = require('../models/response'); const async = require('async'); const log = require('../libs/log')(module); router.post('/login', function (req, res, next) { async.waterfall([ ... ], function(err, results){ let authResponse = new Response(req.session.authenticated, {}, err); res.json(authResponse); }) }
      
      





便宜上、Responseコンストラクターを事前に準備しました。







 const Response = function (authenticated, data, authError) { this.authenticated = authenticated; this.data = data; this.authError = authError; } module.exports = Response;
      
      





async.waterfallの最初の引数として渡された配列に必要な順序で関数を書き込むだけです。 これらのまさにその機能を作成しましょう:







 function findUser(callback){ User.findOne({username: req.body.username}, function (err, user) { if(err) return next(err); (user) ? callback(null, user) : callback('username'); } } function checkPassword(user, callback){ (user.checkPassword(req.body.password)) ? callback(null, user) : callback('password'); } function saveInSession (user, callback){ req.session.authenticated = true; req.session.userId = user.id; callback(null); }
      
      





ここで何が起こっているかを簡単に説明します。データベースでユーザーを検索し、ない場合は「username」エラーでコールバックを呼び出します。検索が成功した場合、ユーザーをコールバックに転送します。 パスワードが正しい場合、再びcheckPasswordメソッドを呼び出し、ユーザーをコールバックに転送します。そうでない場合は、エラー「password」でコールバックを呼び出します。 次に、セッションをデータベースに保存し、最後のコールバックを呼び出します。







以上です! これで、アプリケーションのユーザーはログインできるようになりました。







Angular2とSocket.ioでチャットする



アプリケーションの主要なセマンティックロードを実行する関数を作成するようになりました。 このセクションでは、ダイアログ(チャットルーム)に接続するためのアルゴリズムと、メッセージを送受信するための機能を整理します。 これを行うには、 Socket.ioライブラリを使用します。これにより、ブラウザーとサーバー間のリアルタイムデータ交換を非常に簡単に実装できます。







sockets.jsファイルを作成し、このモジュールをbin / www(Express入力ファイル)に接続します。







 const io = require('../sockets/sockets')(server);
      
      





Socket.ioはweb-socketsプロトコルで動作するため、現在のユーザーのセッションをそれに渡す方法を考え出す必要があります。 これを行うには、すでに作成したsockets.jsファイルに書き込みます。







 const session = require('../libs/session'); module.exports = (function(server) { const io = require('socket.io').listen(server); io.use(function(socket, next) { session(socket.handshake, {}, next); }); return io; });
      
      





Socket.ioは 、ブラウザーとサーバーが常にさまざまなイベントを交換するように設計されています。ブラウザーはサーバーが応答するイベントを生成し、逆にサーバーはブラウザーが応答するイベントを生成します。 クライアント側のイベントハンドラを作成しましょう。







 import { Component } from '@angular/core'; import { Router } from '@angular/router'; declare let io: any; @Component({ ... }) export class ChatFieldComponent { socket: any; constructor(private router: Router, private userDataService: UserDataService){ this.socket = io.connect(); this.socket.on('connect', () => this.joinDialog()); this.socket.on('joined to dialog', (data) => this.getDialog(data)); this.socket.on('message', (data) => this.getMessage(data)); } }
      
      





上記のコードでは、接続、ダイアログへの参加、メッセージの3つのイベントハンドラーを作成しました。 それらのそれぞれは、それに対応する関数を呼び出します。 そのため、connectイベントはjoinDialog()関数を呼び出します。この関数は、対話者のIDを渡すサーバー側の参加ダイアログイベントを生成します。







 joinDialog(){ this.socket.emit('join dialog', this.userDataService.currentOpponent._id); }
      
      





その後、すべてが簡単です:ダイアログに結合されたイベントはユーザーメッセージを含む配列を受け取り、メッセージイベントは新しいメッセージを上記の配列に追加します。







 getDialog(data) => this.dialog = data; getMessage(data) => this.dialog.push(data);
      
      





将来フロントエンドに戻らないように、ユーザーメッセージを送信する関数を作成しましょう。







 sendMessage($event){ $event.preventDefault(); if (this.messageInputQuery !== ''){ this.socket.emit('message', this.messageInputQuery); } this.messageInputQuery = ''; }
      
      





この関数は、メッセージイベントを生成し、それを使用して送信メッセージのテキストを送信します。







残っているのは、サーバー側のイベントハンドラを記述することだけです。







 io.on('connection', function(socket){ let currentDialog, currentOpponent; socket.on('join dialog', function (data) { ... }); socket.on('message', function(data){ ... }); })
      
      





変数currentDialogおよびcurrentOpponentに、現在のダイアログと対話者の識別子を保存します。







対話接続アルゴリズムの作成を始めましょう。 これを行うには、 非同期ライブラリ、つまり前述のwatterfallメソッドを使用します。 アクションのシーケンス:







前のダイアログを終了します。
 function leaveRooms(callback){ //         for(let room in socket.rooms){ socket.leave(room) } //      callback(null); }
      
      



ユーザーとその対話者のデータベースから取得します。
 function findCurrentUsers(callback) { //     : // -    // -    async.parallel([findCurrentUser, findCurrentOpponent], function(err, results){ if (err) callback(err); //    ,      callback(null, results[0], results[1]); }) }
      
      



既存の接続/新しいダイアログの作成:
 function getDialogId(user, opponent, callback){ //       if (user.dialogs[currentOpponent]) { let dialogId = user.dialogs[currentOpponent]; //    Id ,      callback(null, dialogId); } else{ //    : // -   // -      async.waterfall([createDialog, saveDialogIdToUser], function(err, dialogId){ if (err) callback(err); //    Id ,      callback(null, dialogId); }) } }
      
      



メッセージ履歴を取得する:
 function getDialogData(dialogId, callback){ //       Dialog.findById(dialogId, function(err, dialog){ if (err) callback('Error in connecting to dialog'); //    ,      callback(null, dialog); }) }
      
      



上記の関数を呼び出す、グローバルコールバック:
 //     async.waterfall([ leaveRooms, findCurrentUsers, getDialogId, getDialogData ], //   function(err, dialog){ if (err) log.error(err); currentDialog = dialog; //     socket.join(currentDialog.id); //   joined to dialog,       io.sockets.connected[socket.id].emit('joined to dialog', currentDialog.data); } )
      
      





これで、ダイアログに接続するためのアルゴリズムが完成しました;あとは、メッセージイベントのハンドラーを記述するだけです。







 socket.on('message', function(data){ let message = data; let currentUser = socket.handshake.session.userId; let newMessage = new Message(message, currentUser); currentDialog.data.push(newMessage); currentDialog.markModified('data'); currentDialog.save(function(err){ if (err) log.error('Error in saveing dialog =('); io.to(currentDialog.id).emit('message', newMessage); }) })
      
      





このコード例では、メッセージテキストとユーザーIDを変数に保存してから、以前に作成したメッセージコンストラクターを使用して新しいメッセージオブジェクトを作成し、配列に追加し、更新されたダイアログをデータベースに保存し、この部屋でメッセージイベントを生成しました。メッセージ。







これでアプリケーションの準備は完了です!







おわりに



へえ、あなたはまだそれを読んでいますか?! 記事の量にもかかわらず、アプリケーションの作成の詳細をすべて確認する時間はありませんでした。これは、この形式によって私の能力が制限されているためです。 しかし、この作業をしながら、Webプログラミングの分野での知識を大幅に深めただけでなく、作業から多くの喜びを受けました。 みんな、新しいこと、難しいことを恐れることはありません。問題に慎重に取り組み、ポップアップの質問を徐々に理解すると、最初は経験がなくても、本当に良いものを作成できるからです。








All Articles