ライブデモ:リンク
何が新しいものにジャンプしますか?
node.js Webアプリケーション用のボイラープレート。
過去にハッカソンに参加したことがある場合は、プロジェクトを開始するのにどれくらいの時間がかかるかを知っています。ビルドの決定、プログラミング言語の選択、Webフレームワークの選択、CSSフレームワークの選択。しばらくして、GitHubで最初のプロジェクトを開催する可能性があり、その後、他のチームメンバーが貢献し始めることができます。または、 Facebook認証でサインインするような単純なことをするのはどうですか? OAuth 2.0の仕組みに慣れていない場合は、時間を費やすことができます。
私がこのプロジェクトを始めたとき、私の主な焦点はシンプルさと使いやすさにありました。また、Hackathon Webアプリのほとんどのユースケースを具体的にすることなく、可能な限り一般的で再利用可能にしようとしました。最悪の場合、これをプロジェクトの学習ガイドとして使用できます。たとえば、Google認証でのサインインのみに興味がある場合、他には何もありません。
「いいね!そのreadmeだけがすでに金だ!」
- エイドリアン・ル・バス
「すごい。単にすごい。」
- スティーブン・ルーター
「私は今1年間それを使用していますが、多くのプロジェクトです。それは素晴らしいボイラープレートであり、プロジェクトは十分に維持されています!」
- ケビン・グレンジャー
「Sahatのプロジェクトの小さな世界。先週末、HackathonのスターターをHackathonに使用していて、賞品を手に入れました。本当に便利なレポです!」
- 私が一緒に仕事をしていた企業の1つの候補者にインタビューします。
mongodb(ローカルインストールまたはホスト)
node.js 18+
コマンドラインツール
Mac OS X: XCode(またはOS X 10.9+ : xcode-select --install )
Windows: Visual Studio Code + Linux用のWindowsサブシステム - UbuntuまたはVisual Studio
ubuntu / linuxミント: sudo apt-get install build-essential
Fedora : sudo dnf groupinstall "Development Tools"
opensuse: sudo zypper install --type pattern devel_basis
注:ノードまたはエクスプレスを初めて使用する場合は、ノードとエクスプレスの基本を学習するのに役立つnode.js&express from scratusシリーズを見つけることができます。または、完全な初心者向けのもう1つの優れたチュートリアルがあります。Node.js、Express、Mongodbを開始します。
ステップ1:開始する最も簡単な方法は、リポジトリをクローンすることです。
# Get the latest snapshot
git clone https://github.com/sahat/hackathon-starter.git myproject
# Change directory
cd myproject
# Install NPM dependencies
npm install
# Then simply start your app
node app.js注: Nodemonをインストールすることを強くお勧めします。 node.jsアプリの変更を監視し、サーバーを自動的に再起動します。インストールしたら、 node app.jsの代わりにnodemon app.jsを使用します。コードをわずかに変更するたびにサーバーを手動で再起動する必要がないため、長期的には多くの時間を節約できます。インストールするには、 sudo npm install -g nodemon実行します。
ステップ2:必要に応じてAPIキーを取得し、ステップ1を完了してMongoDBをローカルにインストールした後、必要に応じて構成を変更すると、Webブラウザーを介してアプリケーションにアクセスし、ローカルユーザーアカウントを使用できるはずです。ただし、API統合などの特定の機能は、サービスプロバイダーから特定のキーを取得するまで正しく機能しない場合があります。プロジェクトで提供されるキーはプレースホルダーとして機能し、現在利用していない機能のためにそれらを保持できます。取得したキーをアプリケーションに組み込むには、次の2つのオプションがあります。
exportコマンドを使用できます。Facebook_Secret export FACEBOOK_SECRET=xxxxxx 。この方法は、コードリポジトリに秘密を誤って含めるリスクを減らすため、より良い慣行と見なされます。.env.exampleファイルのキーを交換: .env.exampleファイルを開き、新しく取得したものでプレースホルダーキーを更新します。この方法には、レポスをコードするための秘密の偶発的なチェックインのリスクがあります。何を取得して構成するか:
SMTP
Recaptcha
ソーシャルログインのためのoauth( /で /ログインでサインイン)
APIプロバイダーのAPIキーは、それらを使用する予定がある場合の例です。
Mongodb Atlas
電子メールアドレス
ngrokおよびhttps httpsが機能する必要があるAPI(pinterestやfacebookなど)を使用する場合は、ngrokをダウンロードする必要があります。 ngrokを起動し、base_urlを転送アドレス(すなわちhttps://3ccb-1234-abcd.ngrok-free.app )に設定し、転送アドレスを使用してアプリケーションにアクセスします。 ngrokのようなプロキシを使用している場合、https://...ngrok-free.appアドレスの代わりにhttp://localhost:8080でアプリにアクセスしようとすると、CSRFの不一致エラーが発生する可能性があります。
Standalone Ngrokクライアントをインストールまたはダウンロードした後、NGROKを開始して、Linuxの./ngrok http 8080またはWindowsのngrok http 8080でポート8080で交換されたデータをインターセプトできます。
ステップ3:アプリケーションを開発し、エクスペリエンスをカスタマイズします
ステップ4:オプション - 生産への展開を参照してください:
必要なAPIおよびサービスの提供について、適切な資格(クライアントID、クライアントシークレット、APIキー、またはユーザー名&パスワード)を取得する必要があります。詳細については、開始セクションのステップ2を参照してください。
トランザクションメールのプロバイダーからSMTP資格情報を取得します。それに応じて、smtp_user、smtp_password、およびsmtp_host環境変数を設定します。 SMTPホストを選択するときは、アプリがポート465でセキュアなSMTP送信を使用するように構成されていることに留意してください。ニーズに合ったプロバイダーを選択したり、次のプロバイダーのいずれかを利用したりする柔軟性があり、それぞれが便利なために無料の層を提供します。
| プロバイダー | 無料ティア | Webサイト |
|---|---|---|
| sendgrid | 無料で1日100メール/日 | https://sendgrid.com |
| SMTP2GO | 無料で月額1000メール/月 | https://www.smtp2go.com |
| ブレボ | 無料で300メール/日 | https://www.brevo.com |
.envにコピーします。これらのキーは設定の下でアクセスでき、後でもう一度必要な場合はrecaptchaキーがドロップダウンしますhttp://localhost:8080など)http://localhost:8080/auth/google/callback ).envにコピーして貼り付けますhttp://localhost:8080/auth/snapchat/callback )が続くbase_url値を入力します。.envにコピーして貼り付けます.envです.envにコピーして貼り付けます.envですlocalhostを入力しますhttp://localhost:8080など)を入力してくださいhttp://localhost:8080/auth/facebook/callback )が有効なoauthリダイレクトurisの下注: Facebookでのサインインが成功した後、ユーザーはURLに追加されたハッシュ#_=_を使用してホームページにリダイレクトされます。バグではありません。それを処理する方法については、このスタックオーバーフローディスカッションを参照してください。
http://localhost:8080など)を入力します。http://localhost:8080/auth/github/callback ).envファイルにコピーして貼り付けますhttp://localhost:8080など)として設定します。http://localhost:8080/auth/twitter/callback ).envファイルにコピーして貼り付けますhttp://localhost:8080/auth/linkedin/callback )http://localhost:8080など)。r_basicprofile.envファイルにコピーして貼り付けます.envファイルにこれを追加します.envファイルにコピーして貼り付けますhttp://localhost:8080/auth/foursquare/callback ).envファイルにコピーして貼り付けますhttp://localhost:8080/auth/tumblr/callback ).envファイルにコピーして貼り付けます.envファイルにコピーして貼り付けますhttp://localhost:8080/auth/twitch/callback ).envにコピーして貼り付けます.envに貼り付けます。.envファイルにコピーして貼り付けます。.envファイルのクライアントIDとクライアントの秘密を見つけます| 名前 | 説明 |
|---|---|
| config /passport.js | パスポートローカルおよびOAuth戦略、およびログインミドルウェア。 |
| コントローラー/api.js | /APIルートのコントローラーおよびすべてのAPI例。 |
| コントローラー/contact.js | 連絡先フォームのコントローラー。 |
| コントローラー/home.js | ホームページのコントローラー(インデックス)。 |
| コントローラー/user.js | ユーザーアカウント管理のためのコントローラー。 |
| モデル/user.js | ユーザー用のマングーススキーマとモデル。 |
| 公共/ | 静的資産(フォント、CSS、JS、IMG)。 |
| public / js / application.js | クライアント側のJavaScript依存関係を指定します。 |
| public / js /app.js | クライアント側のJavaScriptをここに配置します。 |
| public / css / main.scss | アプリのメインスタイルシート。 |
| ビュー /アカウント/ | ログイン、パスワードリセット、サインアップ、プロファイル用のテンプレート。 |
| ビュー /API / | APIの例のテンプレート。 |
| Views /partials /flash.pug | エラー、情報、および成功フラッシュ通知。 |
| Views /partials /Header.pug | Navbar部分テンプレート。 |
| Views /partials /footer.pug | フッター部分テンプレート。 |
| views /layout.pug | ベーステンプレート。 |
| views /home.pug | ホームページテンプレート。 |
| .Dockerignore | Dockerの使用によって無視されるフォルダーとファイル。 |
| .env.example | APIキー、トークン、パスワード、データベースURI。 |
| .eslintrc | Eslint Linterのルール。 |
| .gitignore | gitで無視されたフォルダーとファイル。 |
| app.js | メインアプリケーションファイル。 |
| docker-compose.yml | Dockerは構成ファイルを作成します。 |
| dockerfile | Docker構成ファイル。 |
| package.json | NPM依存関係。 |
| Package-lock.json | package.jsonにnpm依存関係の正確なバージョンが含まれています。 |
注:意見の名前または構成方法については、好みはありません。すべてのテンプレートを、ネストされたフォルダー構造を持たずにトップレベルのviewsディレクトリに配置できます。 extends ../layoutを更新することを忘れないでくださいres.render()
| パッケージ | 説明 |
|---|---|
| @fortawesome/fontawesome-free | シンボルとアイコンライブラリ。 |
| @googleapis/drive | GoogleドライブAPI統合ライブラリ。 |
| @googleapis/sheets | GoogleシートAPI統合ライブラリ。 |
| @ladjs/bootstrap-social | ソーシャルボタンライブラリ。 |
| @lob/lob-typescript-sdk | LOB(USPSメーリング /物理メーリングサービス)ライブラリ。 |
| @node-rs/bcrypt | ハッシュおよび塩漬けのユーザーパスワード用のライブラリ。 |
| @octokit/rest | Github APIライブラリ。 |
| @passport-js/passport-twitter | X(Twitter)ログインサポート(OAUTH 2)。 |
| @popperjs/core | ポッパーとツールチップ用のフロントエンドJSライブラリ。 |
| axios | HTTPクライアント。 |
| ボディパーサー | node.jsボディの解析ミドルウェア。 |
| ブートストラップ | CSSフレームワーク。 |
| チャイ | BDD/TDDアサーションライブラリ。 |
| チェリオ | jQueryスタイルの構文を使用して、Webページをスクレイプします。 |
| 圧縮 | node.js圧縮ミドルウェア。 |
| Connect-Mongo | ExpressのMongodbセッションストア。 |
| dotenv | .ENVファイルから環境変数をロードします。 |
| エラーハンドラー | 開発のみのエラーハンドラーミドルウェア。 |
| eslint | リナーJavaScript。 |
| eslint-config-airbnb-base | Airbnbによる構成Eslint。 |
| eslint-plugin-chai-フレンドリー | Chai.jsの「期待」と「Suff」ステートメントに向けてEslintに優しいものにします。 |
| Eslint-Plugin-Import | 適切な輸入を検証するのに役立つルールを備えたESLINTプラグイン。 |
| 急行 | node.js Webフレームワーク。 |
| エクスプレスフラッシュ | Expressのフラッシュメッセージを提供します。 |
| エクスプレスレートリミット | 虐待保護のためにミドルウェアを制限するレート。 |
| エクスプレスセッション | Express用のシンプルなセッションミドルウェア。 |
| ハスキー | gitフックマネージャーは、gitでタスクを自動化します。 |
| jquery | HTML要素と対話するためのフロントエンドJSライブラリ。 |
| lastfm | last.fm APIライブラリ。 |
| リントステージ | gitによってステージングされたリントファイルへのユーティリティ。 |
| ロブ | LOB APIライブラリ。 |
| ロダッシュ | 配列、数字、オブジェクト、文字列を使用するためのユーティリティライブラリ。 |
| ルスカ | CSRFミドルウェア。 |
| Mailchecker | メールアドレスが有効であり、使い捨てアドレスではないことを確認します。 |
| モカ | テストフレームワーク。 |
| 一瞬 | 解析、検証、計算日と時間。 |
| mongodbmemoryserver | メモリ内のmongodb(実行中のdbなしでテストを実行する場合)。 |
| マングース | Mongodb ODM。 |
| モーガン | http要求node.jsのロガーミドルウェア |
| マルター | node.js multipart/form-dataを処理するためのミドルウェア。 |
| ノードメール | node.js電子メールを送信するためのライブラリ。 |
| NYC | カバレッジテスト。 |
| パスポート | node.jsのシンプルでエレガントな認証ライブラリ |
| パスポートフラック | Facebookプラグインを備えたサインイン。 |
| Passport-Github2 | GitHubプラグインを備えたサインイン。 |
| パスポートグーグルオース | Googleプラグインでサインインします。 |
| Passport-Linkedin-Oauth2 | LinkedInプラグインを備えたサインイン。 |
| パスポート - ローカル | ユーザー名とパスワードプラグインを備えたサインイン。 |
| Passport-oauth | 独自のOAUTH 1.0AおよびOAUTH 2.0戦略をセットアップできます。 |
| Passport-oauth2-refresh | OAUTH 2.0を更新するライブラリは、トークンを更新してトークンにアクセスします。 |
| パスポートスナプチャット | Snapchatプラグインを備えたサインイン。 |
| パスポートスチームオペニッド | OpenID 2.0スチームプラグイン。 |
| パッチパッケージ | メンテナーによる修正に先立って、壊れたノードモジュールを修正します。 |
| PayPal-Rest-Sdk | PayPal APISライブラリ。 |
| パグ | Express用のテンプレートエンジン。 |
| サス | SASSコンパイラスーパーパワーを備えたCSSを生成します |
| シノン | JavaScriptのスパイ、スタブ、モックをテストします。 |
| ストライプ | オフカルストライプAPIライブラリ。 |
| スーパーテスト | HTTPアサーションライブラリ。 |
| トワイリオ | Twilio APIライブラリ。 |
| Twitch-Passport | Twitchプラグインを備えたサインイン。 |
| バリデーター | 文字列のバリデーターと消毒剤のライブラリ。 |
filesize(265318); // "265.32 kB" 。var token = _.find(req.user.tokens, { kind: 'twitter' }); 、1番目のパラメーターは配列であり、2番目のパラメーターは検索するオブジェクトです。 403 Error: Forbidden ?次の隠し入力要素をフォームに追加する必要があります。これは、CSRF保護の一部としてプルリクエスト#40に追加されました。
input(type='hidden', name='_csrf', value=_csrf)
注:特定のURLをホワイトリストすることが可能になりました。つまり、CSRF検証チェックをバイパスする必要があるルートのリストを指定できます。
注2:ホワイトリストの動的URLには、CSRFミドルウェア内の正規表現テストを使用して、 req.originalUrl目的のパターンと一致するかどうかを確認します。
これは、 app.jsで定義されたカスタムエラーメッセージであり、mongodbに接続する問題があることを示しています。
mongoose . connection . on ( 'error' , ( err ) => {
console . error ( err ) ;
console . log ( '%s MongoDB connection error. Please make sure MongoDB is running.' , chalk . red ( '✗' ) ) ;
process . exit ( ) ;
} ) ; app.jsを起動する前に、mongodbサーバーを実行する必要があります。 Mongodbをこちらからダウンロードするか、パッケージマネージャーを介してインストールできます。 Windowsユーザー、WindowsにMongoDBをインストールすることをお読みください。
ヒント:常にインターネットに接続している場合は、Mongodbをローカルにダウンロードしてインストールする代わりに、Mongodb Atlasを使用できます。 .envファイルでデータベース資格情報を更新するだけでいいでしょう。
データベースURIを.envで変更していない可能性があります。 MONGODBがlocalhostに設定されている場合、MongoDBが実行されている限り、マシンでのみ動作します。レンダリング、OpenShift、または他のプロバイダーに展開すると、 localhostでmongodbを実行することはできません。 Mongodb Atlasを使用してアカウントを作成してから、無料のティアデータベースを作成する必要があります。アカウントのセットアップ方法と、Mongodb Atlasとの新しいデータベースの段階的なデータベースについては、展開を参照してください。
簡単にするために。このブログで概説されているように、各コントローラーにappコンテキストを渡すなど、より良いアプローチがあるかもしれませんが、初心者にとっては混乱を招くようなスタイルがあると思います。 exportsとmodule.exportsの概念を把握するのに長い時間がかかりました。他のファイルにグローバルなappリファレンスを持っていることは言うまでもなく。私にとっては、後方に考えています。 app.jsは「アプリの中心」であり、モデル、ルート、コントローラーなどを参照するものである必要があります。小さなプロジェクトでソロ作業するときは、このREST APIサーバーの場合と同様に、すべてをapp.js内に持っていることを好みます。
このセクションは、特定の機能がどのように機能するかについての詳細な説明を提供することを目的としています。たぶん、あなたはそれがどのように機能するかに興味があるかもしれませんし、おそらくあなたはコードを読んでいる間に失われ混乱しているかもしれません、私はそれがあなたにいくらかのガイダンスを提供することを願っています。
HTML5 UPには、無料でダウンロードできる多くの美しいテンプレートがあります。
zipファイルをダウンロードすると、 index.html 、画像、 CSS 、およびJSフォルダーが付属します。それでは、どのようにハッカソンスターターと統合しますか? Hackathon StarterはBootstrap CSSフレームワークを使用しますが、これらのテンプレートは使用しません。両方のCSSファイルを同時に使用しようとすると、望ましくない効果が発生する可能性があります。
注:カスタムテンプレートアプローチを使用して、作成したビュー、レイアウト、ホームページ、APIブラウザー、ログイン、サインアップ、アカウント管理、連絡先のいずれも再利用できないことを理解する必要があります。これらのビューは、ブートストラップグリッドとスタイルを使用して構築されました。テンプレートで提供される別の構文を使用して、グリッドを手動で更新する必要があります。そうは言っても、そうしたい場合は、メインアプリインターフェイスにブートストラップを使用し、ランディングページにカスタムテンプレートを使用します。
最初から始めましょう。この例では、エスケープ速度テンプレートを使用します。
注:簡単にするために、 index.htmlのみを検討し、 left-sidebar.html 、 no-sidebar.html 、 right-sidebar.htmlをスキップします。
すべてのJavaScriptファイルをhtml5up-escape-velocity/jsからpublic/jsに移動します。次に、すべてのCSSファイルをhtml5up-escape-velocity/cssからpublic/cssに移動します。そして最後に、すべての画像をhtml5up-escape-velocity/imagesからpublic/imagesに移動します。既存のIMGフォルダーに移動できますが、すべてのimgリファレンスを手動で変更する必要があります。 index.htmlの内容をつかみ、それをHTMLに貼り付けてパグします。
注:それに応じて、すべてのCSSおよびJSパスを更新することを忘れないでください。
新しいファイルescape-velocity.pugを作成します。 viewsフォルダーでパグマークアップをパグして貼り付けます。コードres.render('account/login')が表示されるときはいつでも、 views/account/login.pugファイルを検索することを意味します。
それがどのように見えるか見てみましょう。コントローラー内の新しいコントローラーEscapeVelocityを作成controllers/home.js :
exports . escapeVelocity = ( req , res ) => {
res . render ( 'escape-velocity' , {
title : 'Landing Page'
} ) ;
} ;そして、 app.jsにルートを作成します。インデックスコントローラーの直後に配置しました:
app . get ( '/escape-velocity' , homeController . escapeVelocity ) ;サーバーを再起動します( Nodemonを使用していない場合);次に、 http://localhost:8080/escape-velocityに新しいテンプレートが表示されます
ここで停止しますが、このテンプレートpartials/header.pug単なる1ページ以上のものとして使用したい場合は、これらのパグテンプレートの仕組みを確認してください: layout.pug -base partials/footer.pug 、 index.pug 。あなたはそれを手動で小さな部分に分解する必要があります。すべてのページで同じように保持するテンプレートのどの部分を把握します - それがあなたの新しいlayout.pugです。次に、変更する各ページ、 index.pug 、 about.pug 、 contact.pugは、 block contentを介して新しいlayout.pugに埋め込まれます。既存のテンプレートを参照として使用します。
これはかなり長いプロセスであり、他の場所から得るテンプレートにはさらに別のグリッドシステムがあるかもしれません。だから私はハッカソンのスターターのためにブートストラップを選びました。多くの人がすでにブートストラップに精通していることに加えて、ブートストラップを使用したことがない場合は、簡単に始めることができます。また、Themeforestで美しくデザインされた多くのブートストラップテーマを購入し、ハッカソンスターターのドロップイン代替品として使用することもできます。ただし、完全にカスタムのHTML/CSSデザインを使用したい場合は、開始するのに役立ちます。
フラッシュメッセージを使用すると、リクエストの最後にメッセージを表示し、次のリクエストと次のリクエストのみにアクセスできます。たとえば、失敗したログインの試行では、エラーメッセージでアラートを表示しますが、そのページを更新するか、別のページにアクセスしてログインページに戻るとすぐに、そのエラーメッセージが消えます。 1回だけ表示されます。このプロジェクトは、フラッシュメッセージにExpress-Flashモジュールを使用します。そして、そのモジュールはConnect-Flashの上に構築されています。これは、最初にこのプロジェクトで使用したものです。 Express-Flashを使用すると、 res.render()内のすべてのビューにフラッシュメッセージを明示的に送信する必要はありません。すべてのフラッシュメッセージは、 Express-Flashのおかげで、デフォルトでmessagesオブジェクトを介してビューで使用できます。
フラッシュメッセージには2段階のプロセスがあります。 req.flash('errors', { msg: 'Error messages goes here' }を使用して、コントローラーにフラッシュメッセージを作成し、ビューに表示します。
if messages . errors
.alert.alert-danger.fade.in
for error in messages . errors
div = error . msg最初のステップでは、 'errors'はフラッシュメッセージの名前であり、ビューのmessagesオブジェクトのプロパティの名前と一致するはずです。 Flashメッセージが存在することを表示したくないため、 if message.errorsを内部に配置します。 { msg: 'Error message goes here' }などのエラーを渡す理由は、文字列だけでなく、 'Error message goes here'は、一貫性のためです。それを明確にするために、ユーザーの入力の検証とサニタイズに使用されるExpress-Validatorモジュールは、すべてのエラーをオブジェクトの配列として返します。各オブジェクトには、エラーが発生した理由のメッセージを含むmsgプロパティがあります。エラーが存在するときにExpress-Validatorが返すもののより一般的な例を次に示します。
[
{ param : "name" , msg : "Name is required" , value : "<received input>" } ,
{ param : "email" , msg : "A valid email is required" , value : "<received input>" }
]そのスタイルと一致するようにするには、すべてのフラッシュメッセージを文字列の代わりに{ msg: 'My flash message' }として渡す必要があります。それ以外の場合は、エラーメッセージのないアラートボックスが表示されます。これは、 partials/flash.pugテンプレートでは、 error.msg (つまり"My flash message".msg )を出力しようとするためです。つまり、文字列オブジェクトのmsgメソッドを呼び出しようとします。エラーについて私が先ほど言及したことはすべて、「情報」と「成功」フラッシュメッセージにも適用されます。また、次のような新しいものを作成することもできます。
データ使用コントローラー(例)
req.flash('warning', { msg: 'You have exceeded 90% of your data usage' });
ユーザーアカウントページ(例)
if messages . warning
.alert.alert-warning.fade.in
for warning in messages . warning
div = warning . msg partials/flash.pugは、フラッシュメッセージのフォーマット方法を含む部分テンプレートです。以前は、フラッシュメッセージ(連絡先、ログイン、サインアップ、プロファイル)を使用する各ビュー全体にフラッシュメッセージが散らばっていましたが、ありがたいことにドライアプローチを使用しています。
Flashメッセージの部分テンプレートは、フッターとナビゲーションとともに、 layout.pugに含まれています。
body
include partials/header
.container
include partials/flash
block content
include partials/footerFlashメッセージについてさらに質問がある場合は、お気軽に問題を開いてください。これに応じてこのミニガイドを更新するか、見逃したものを含めたい場合はプルリクエストを送信します。
これが「新しいルートを作成するにはどうすればよいですか?」 The main file app.js contains all the routes. Each route has a callback function associated with it. Sometimes you will see three or more arguments for a route. In a case like that, the first argument is still a URL string, while middle arguments are what's called middleware. Think of middleware as a door. If this door prevents you from continuing forward, you won't get to your callback function. One such example is a route that requires authentication.
app . get ( '/account' , passportConfig . isAuthenticated , userController . getAccount ) ; It always goes from left to right. A user visits /account page. Then isAuthenticated middleware checks if you are authenticated:
exports . isAuthenticated = ( req , res , next ) => {
if ( req . isAuthenticated ( ) ) {
return next ( ) ;
}
res . redirect ( '/login' ) ;
} ; If you are authenticated, you let this visitor pass through your "door" by calling return next(); 。 It then proceeds to the next middleware until it reaches the last argument, which is a callback function that typically renders a template on GET requests or redirects on POST requests. In this case, if you are authenticated, you will be redirected to the Account Management page; otherwise, you will be redirected to the Login page.
exports . getAccount = ( req , res ) => {
res . render ( 'account/profile' , {
title : 'Account Management'
} ) ;
} ; Express.js has app.get , app.post , app.put , app.delete , but for the most part, you will only use the first two HTTP verbs, unless you are building a RESTful API. If you just want to display a page, then use GET , if you are submitting a form, sending a file then use POST .
Here is a typical workflow for adding new routes to your application. Let's say we are building a page that lists all books from the database.
Step 1. Start by defining a route.
app . get ( '/books' , bookController . getBooks ) ;Note: As of Express 4.x you can define your routes like so:
app . route ( '/books' )
. get ( bookController . getBooks )
. post ( bookController . createBooks )
. put ( bookController . updateBooks )
. delete ( bookController . deleteBooks )And here is how a route would look if it required an authentication and an authorization middleware:
app . route ( '/api/twitter' )
. all ( passportConfig . isAuthenticated )
. all ( passportConfig . isAuthorized )
. get ( apiController . getTwitter )
. post ( apiController . postTwitter ) Use whichever style makes sense to you. Either one is acceptable. I think that chaining HTTP verbs on app.route is a very clean and elegant approach, but on the other hand, I can no longer see all my routes at a glance when you have one route per line.
Step 2. Create a new schema and a model Book.js inside the models directory.
const mongoose = require ( 'mongoose' ) ;
const bookSchema = new mongoose . Schema ( {
name : String
} ) ;
const Book = mongoose . model ( 'Book' , bookSchema ) ;
module . exports = Book ; Step 3. Create a new controller file called book.js inside the controllers directory.
/**
* GET /books
* List all books.
*/
const Book = require ( '../models/Book.js' ) ;
exports . getBooks = ( req , res ) => {
Book . find ( ( err , docs ) => {
res . render ( 'books' , { books : docs } ) ;
} ) ;
} ; Step 4. Import that controller in app.js .
const bookController = require ( './controllers/book' ) ; Step 5. Create books.pug template.
extends layout
block content
.page-header
h3 All Books
ul
for book in books
li = book . nameそれでおしまい! I will say that you could have combined Step 1, 2, 3 as following:
app . get ( '/books' , ( req , res ) => {
Book . find ( ( err , docs ) => {
res . render ( 'books' , { books : docs } ) ;
} ) ;
} ) ; Sure, it's simpler, but as soon as you pass 1000 lines of code in app.js it becomes a little challenging to navigate the file. I mean, the whole point of this boilerplate project was to separate concerns, so you could work with your teammates without running into MERGE CONFLICTS . Imagine you have four developers working on a single app.js , I promise you it won't be fun resolving merge conflicts all the time. If you are the only developer, then it's okay. But as I said, once it gets up to a certain LoC size, it becomes difficult to maintain everything in a single file.
That's all there is to it. Express.js is super simple to use. Most of the time you will be dealing with other APIs to do the real work: Mongoose for querying database, socket.io for sending and receiving messages over WebSockets, sending emails via Nodemailer, form validation using validator.js library, parsing websites using Cheerio, etc.
Dan Stroot submitted an excellent pull request that adds a real-time dashboard with socket.io. And as much as I'd like to add it to the project, I think it violates one of the main principles of the Hackathon Starter:
When I started this project, my primary focus was on simplicity and ease of use. I also tried to make it as generic and reusable as possible to cover most use cases of hackathon web apps, without being too specific .
When I need to use socket.io, I really need it, but most of the time - I don't. But more importantly, WebSockets support is still experimental on most hosting providers. Due to past provider issues with WebSockets, I have not include socket.io as part of the Hackathon Starter. For now... If you need to use socket.io in your app, please continue reading.
First, you need to install socket.io:
npm install socket . io Replace const app = express();次のコードで:
const app = express ( ) ;
const server = require ( 'http' ) . Server ( app ) ;
const io = require ( 'socket.io' ) ( server ) ; I like to have the following code organization in app.js (from top to bottom): module dependencies, import controllers, import configs, connect to database, express configuration, routes, start the server, socket.io stuff. That way I always know where to look for things.
Add the following code at the end of app.js :
io . on ( 'connection' , ( socket ) => {
socket . emit ( 'greet' , { hello : 'Hey there browser!' } ) ;
socket . on ( 'respond' , ( data ) => {
console . log ( data ) ;
} ) ;
socket . on ( 'disconnect' , ( ) => {
console . log ( 'Socket disconnected' ) ;
} ) ;
} ) ;One last thing left to change:
app . listen ( app . get ( 'port' ) , ( ) => {に
server . listen ( app . get ( 'port' ) , ( ) => {At this point, we are done with the back-end.
You now have a choice - to include your JavaScript code in Pug templates or have all your client-side JavaScript in a separate file - in app.js . I admit, when I first started with Node.js and JavaScript in general, I placed all JavaScript code inside templates because I have access to template variables passed in from Express right then and there. It's the easiest thing you can do, but also the least efficient and harder to maintain. Since then I almost never include inline JavaScript inside templates anymore.
But it's also understandable if you want to take the easier road. Most of the time you don't even care about performance during hackathons, you just want to "get shit done" before the time runs out. Well, either way, use whichever approach makes more sense to you. At the end of the day, it's what you build that matters, not how you build it.
If you want to stick all your JavaScript inside templates, then in layout.pug - your main template file, add this to head block.
script ( src = '/socket.io/socket.io.js' )
script .
let socket = io . connect ( window . location . href );
socket . on ( ' greet ' , function ( data ) {
console . log (data);
socket . emit ( ' respond ' , { message : ' Hey there, server! ' });
}); Note: Notice the path of the socket.io.js , you don't actually have to have socket.io.js file anywhere in your project; it will be generated automatically at runtime.
If you want to have JavaScript code separate from templates, move that inline script code into app.js , inside the $(document).ready() function:
$ ( document ) . ready ( function ( ) {
// Place JavaScript code here...
let socket = io . connect ( window . location . href ) ;
socket . on ( 'greet' , function ( data ) {
console . log ( data ) ;
socket . emit ( 'respond' , { message : 'Hey there, server!' } ) ;
} ) ;
} ) ;And we are done!
Declares a read-only named constant.
const name = 'yourName' ;Declares a block scope local variable.
let index = 0 ; Using the `${}` syntax, strings can embed expressions.
const name = 'Oggy' ;
const age = 3 ;
console . log ( `My cat is named ${ name } and is ${ age } years old.` ) ; To import functions, objects, or primitives exported from an external module. These are the most common types of importing.
const name = require ( 'module-name' ) ; const { foo , bar } = require ( 'module-name' ) ;To export functions, objects, or primitives from a given file or module.
module . exports = { myFunction } ; module . exports . name = 'yourName' ; module . exports = myFunctionOrClass ; The spread operator allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) are expected.
myFunction ( ... iterableObject ) ; < ChildComponent { ... this . props } /> A Promise is used in asynchronous computations to represent an operation that hasn't completed yet but is expected in the future.
var p = new Promise ( function ( resolve , reject ) { } ) ; The catch() method returns a Promise and deals with rejected cases only.
p . catch ( function ( reason ) { /* handle rejection */ } ) ; The then() method returns a Promise. It takes two arguments: callback for the success & failure cases.
p . then ( function ( value ) { /* handle fulfillment */ } , function ( reason ) { /* handle rejection */ } ) ; The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved or rejects with the reason of the first passed promise that rejects.
Promise . all ( [ p1 , p2 , p3 ] ) . then ( function ( values ) { console . log ( values ) } ) ; Arrow function expression. Shorter syntax & lexically binds the this value. Arrow functions are anonymous.
singleParam => { statements } ( ) => { statements } ( param1 , param2 ) => expression const arr = [ 1 , 2 , 3 , 4 , 5 ] ;
const squares = arr . map ( x => x * x ) ; The class declaration creates a new class using prototype-based inheritance.
class Person {
constructor ( name , age , gender ) {
this . name = name ;
this . age = age ;
this . gender = gender ;
}
incrementAge ( ) {
this . age ++ ;
}
}? Credits : DuckDuckGo and @DrkSephy.
? back to top
Math . floor ( Date . now ( ) / 1000 ) ; moment().unix();
var now = new Date ( ) ;
now . setMinutes ( now . getMinutes ( ) + 30 ) ; moment().add(30, 'minutes');
// DD-MM-YYYY
var now = new Date ( ) ;
var DD = now . getDate ( ) ;
var MM = now . getMonth ( ) + 1 ;
var YYYY = now . getFullYear ( ) ;
if ( DD < 10 ) {
DD = '0' + DD ;
}
if ( MM < 10 ) {
MM = '0' + MM ;
}
console . log ( MM + '-' + DD + '-' + YYYY ) ; // 03-30-2016 console.log(moment(new Date(), 'MM-DD-YYYY'));
// hh:mm (12 hour time with am/pm)
var now = new Date ( ) ;
var hours = now . getHours ( ) ;
var minutes = now . getMinutes ( ) ;
var amPm = hours >= 12 ? 'pm' : 'am' ;
hours = hours % 12 ;
hours = hours ? hours : 12 ;
minutes = minutes < 10 ? '0' + minutes : minutes ;
console . log ( hours + ':' + minutes + ' ' + amPm ) ; // 1:43 am console.log(moment(new Date(), 'hh:mm A'));
var today = new Date ( ) ;
var nextWeek = new Date ( today . getTime ( ) + 7 * 24 * 60 * 60 * 1000 ) ; moment().add(7, 'days');
var today = new Date ( ) ;
var yesterday = date . setDate ( date . getDate ( ) - 1 ) ; moment().add(-1, 'days');
? back to top
User . find ( ( err , users ) => {
console . log ( users ) ;
} ) ; let userEmail = '[email protected]' ;
User . findOne ( { email : userEmail } , ( err , user ) => {
console . log ( user ) ;
} ) ; User
. find ( )
. sort ( { _id : - 1 } )
. limit ( 5 )
. exec ( ( err , users ) => {
console . log ( users ) ;
} ) ; Let's suppose that each user has a votes field and you would like to count the total number of votes in your database across all users. One very inefficient way would be to loop through each document and manually accumulate the count. Or you could use MongoDB Aggregation Framework instead:
User . aggregate ( { $group : { _id : null , total : { $sum : '$votes' } } } , ( err , votesCount ) => {
console . log ( votesCount . total ) ;
} ) ;? back to top
You will need to install docker and docker-compose on your system. If you are using WSL, you will need to install Docker Desktop on Windows and docker-compose on WSL.
Docker installation
Common problems setting up docker
After installing docker, start the application with the following commands :
# To build the project while supressing most of the build messages
docker-compose build web
# To build the project without supressing the build messages or using cached data
docker-compose build --no-cache --progress=plain web
# To start the application (or to restart after making changes to the source code)
docker-compose up web
To view the app, find your docker IP address + port 8080 ( this will typically be http://localhost:8080/ ). To use a port other than 8080, you would need to modify the port in app.js, Dockerfile, and docker-compose.yml.
Once you are ready to deploy your app, you will need to create an account with a cloud platform to host it. These are not the only choices, but they are my top picks. Additionally, you can create an account with MongoDB Atlas and then pick one of the providers below. Again, there are plenty of other choices, and you are not limited to just the ones listed below.
Render provides free nodejs hosting for repos on Github and Gitlab.
0.0.0.0/0 . Click SAVE to save the 0.0.0.0/0 whitelist..env.example with this URI string. Make sure to replace the with the db User password that you created under the Security tab.We are deploying your changes . You will need to wait for the deployment to finish before using the DB in your application.sudo gem install rhc ?rhc login and enter your OpenShift credentialsrhc app create MyApp nodejs-0.10git remote add openshift YOUR_GIT_REMOTE Add these two lines to app.js , just place them anywhere before app.listen() :
var IP_ADDRESS = process . env . OPENSHIFT_NODEJS_IP || '127.0.0.1' ;
var PORT = process . env . OPENSHIFT_NODEJS_PORT || 8080 ; Then change app.listen() to:
app . listen ( PORT , IP_ADDRESS , ( ) => {
console . log ( `Express server listening on port ${ PORT } in ${ app . settings . env } mode` ) ;
} ) ; Add this to package.json , after name and version . This is necessary because, by default, OpenShift looks for server.js file. And by specifying supervisor app.js it will automatically restart the server when node.js process crashes.
"main" : "app.js" ,
"scripts" : {
"start" : "supervisor app.js"
} ,git push -f openshift master-f (force) flag because OpenShift creates a dummy server with the welcome page when you create a new Node.js app. Passing -f flag will override everything with your Hackathon Starter project repository. Do not run git pull as it will create unnecessary merge conflicts.git remote add azure [Azure Git URL]git push azure masterNOTE At this point it appears that Bluemix's free tier to host NodeJS apps is limited to 30 days. If you are looking for a free tier service to host your app, Render might be a better choice at this point
Create a Bluemix Account
Sign up for Bluemix, or use an existing account.
Download and install the Cloud Foundry CLI to push your applications to Bluemix.
Create a manifest.yml file in the root of your application.
applications:
- name: <your-app-name>
host: <your-app-host>
memory: 128M
services:
- myMongo-db-name
The host you use will determinate your application URL initially, eg <host>.mybluemix.net . The service name 'myMongo-db-name' is a declaration of your MongoDB service. If you are using other services like Watson for example, then you would declare them the same way.
$ cf login -a https://api.ng.bluemix.net
$ cf create-service mongodb 100 [your-service-name]
Note: this is a free and experiment verion of MongoDB instance. Use the MongoDB by Compose instance for production applications:
$ cf create-service compose-for-mongodb Standard [your-service-name]'
Push the application
$ cf push
$ cf env <your-app-name >
(To view the *environment variables* created for your application)
Done , now go to the staging domain ( <host>.mybluemix.net ) and see your app running.
Be sure to check out the full list of Watson services to forwarder enhance your application functionality with a little effort. Watson services are easy to get going; it is simply a RESTful API call. Here is an example of a Watson Toner Analyzer to understand the emotional context of a piece of text that you send to Watson.
Virtual Assistant - Deliver consistent and intelligent customer care across all channels and touchpoints with conversational AI.
Natural Language Understanding - Analyze text to extract meta-data from content such as concepts, entities, keywords and more.
Discovery - Accelerate business decisions and processes with an AI-powered intelligent document understanding and content analysis platform.
Orchestrate - Hand off tedious tasks to Watson and never work the same way again.
List of Watson Services.
Download and install Node.js
Select or create a Google Cloud Platform Console project
Enable billing for your project (there's a $300 free trial)
Install and initialize the Google Cloud SDK
Create an app.yaml file at the root of your hackathon-starter folder with the following contents:
runtime : nodejs
env : flex
manual_scaling :
instances : 1 Make sure you've set MONGODB_URI in .env.example
Run the following command to deploy the hackathon-starter app:
gcloud app deployMonitor your deployed app in the Cloud Console
View the logs for your app in the Cloud Console
If you are starting with this boilerplate to build an application for prod deployment, or if after your hackathon you would like to get your project hardened for production use, see prod-checklist.md.
You can find the changelog for the project in: CHANGELOG.md
If something is unclear, confusing, or needs to be refactored, please let me know. Pull requests are always welcome, but due to the opinionated nature of this project, I cannot accept every pull request. Please open an issue before submitting a pull request. This project uses Airbnb JavaScript Style Guide with a few minor exceptions. If you are submitting a pull request that involves Pug templates, please make sure you are using spaces , not tabs.
The MIT License (MIT)
Copyright (c) 2014-2023 Sahat Yalkabov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.