前言
首先,我也不知道會用這份筆記當作我的第一篇筆記,從2020年疫情以來就有想寫的念頭,但都被自己的惰性或身邊發生的事物而拖延,直到2021年,一個對我很不一樣的一年,身上多了不少責任,為了更加鞭策自己,嘗試看看寫筆記,並給自己訂了2021的目標,希望今年可以產出40篇以上的文章心得,那就以這份筆記當作一個好的開始吧。
什麼是Node.js 及 Express.js?
擷取自WIKI
Node.js 是能夠在伺服器端運行 JavaScript 的開放原始碼、跨平台 JavaScript 執行環境。
重點: Node.js是一個執行環境Express.js 簡稱Express,是針對Node.js的web應用框架,在MIT許可證下作為自由及開放原始碼軟體發行。
重點: 簡稱Express.js是一個web應用框架
簡單來說,原本的JavaScript是以前端程式設計所使用,而Node.js是以JavaScript為基礎所製成的伺服器端的執行環境。Express則是在Node.js的環境上,作為一個框架,方便讓人快速打造專案網站。
有興趣其內容的人可以再參考wiki。
資料來源: Node.js_wiki、Express.js_wiki
安裝準備
首先我們會用到的工具有很多,包含Node.js, VScode, NPM, Express, Handlebars(樣本系統)。
Node.js, VScode, NPM 可以直接從官網Download安裝,接下來會以開發的專案的流程開始進行。
專案開始 - Restaurant List
這是一個利用 Express.js、Bootstrap、Handlebars 所製作的網站,在這份專案中會了解到如何設定路由、製作 Handlebars 樣板、發送及接受 Handlebars 參數顯示動態資料、Handlebars 的迴圈應用等技巧。
Installation
開好VScode,並且輸入Ctrl
+ Shift
+ ``
,打開Terminal,輸入以下資訊
#移動並創建本地資料夾
mkdir -p /installation/path && cd /installation/path
#從package.json中安裝express、express-handlebars套件
npm install express express-handlebars nodemon
Download Bootstrap, Popper.js, jQuery的js及CSS檔,或者利用CDN載入也可以
Features
- 利用 Bootstrap 製作 RWD 網站樣式
- 使用 Express.js、Handlebars 製作網站及路由設定
- 把 JSON 資料帶入 Handlebars 樣板中動態呈現
- 用 Query String 打造搜尋功能
- 將網頁依照 Layouts 拆成多個部分樣版的 hadlebars 方便維護
Tools
- Express - 應用程式框架
- Handlebars - web 模板系統
- Bootstrap - 開源前端框架
檔案架構
流程
內容元件化
將內容分為header, footer, searchBar元件,show, index為主頁、結果分頁
將元件丟入partials
資料夾,show及index放入views
,將最重要的main放入layouts
資料夾組織成一個網站。
載入Bootstrap, Popper.js, jQuery的js及CSS檔
將Bootstrap Popper.js jQuery的js、CSS檔放入public/javascripts
和public/samplesheets
中。
設定路由(app.js)
重頭戲來啦!先載入Express跟Handlebars、設定Server Port、template engine及靜態檔案
- Handlebars預設載入main.handlebars
- 利用
static
讓Express不針對public
資料夾進行處理。
//載入express
const express = require('express')
const app = express()
//設定port
const port = 3000
//載入handlebars
const exphbs = require('express-handlebars')
// setting template engine
app.engine('handlebars', exphbs({ defaultLayout: 'main' }))
app.set('view engine', 'handlebars')
// setting static files
app.use(express.static('public'))
使用get method設定網頁路由
- 第5行 利用準備渲染網頁的資料(res)進行index網頁渲染,並將restaurantList.results以restaurants變數傳到index.handlebars。
- 第8行
/restaurants/:restaurant_id
中的:restaurant_id
為從網頁request回來的參數,可進行後續資料處理,最後render到show.handlebars。 - 第22行 render到index.handlebars中同時有兩個參數,一個是restaurants;另一個是keyword。
const restaurantList = require('./restaurant.json')
// routes setting
app.get('/', (req, res) => {
// paste the restaurant data into 'index' partial template
res.render('index', { restaurants: restaurantList.results })
})
app.get('/restaurants/:restaurant_id', (req, res) => {
// find the restaurant_id in restaurantList and render 'show' partial template
const restaurant = restaurantList.results.find((restaurant) => restaurant.id.toString() === req.params.restaurant_id)
res.render('show', { restaurant: restaurant })
})
app.get('/search', (req, res) => {
// console.log(req.query);
// get user query string and filter restaurantList data
const keyword = req.query.keyword
const restaurants = restaurantList.results.filter((restaurant) => {
return restaurant.name.toLowerCase().includes(keyword.toLowerCase())
})
res.render('index', { restaurants: restaurants, keyword: keyword })
})
設定main.handlebars
我們可以看到在<body>
中有出現{{> header}}
, {{{ body }}}
,{{}}
是handlebars解析純文字使用,而{{{}}}
是handlebars解析純html使用,而{{> header}}
是handlebars解析/public/partials/header.handlebars
時,需透過>
來指定檔案路徑解析header.handlebars。
- 記得網頁還是需要經過
reset.css
的處理。 - 網頁使用icons需載入fontawesome。
- 網頁使用RWD需載入Bootstrap。
載入所需的css在<head>
中
<!-- ./views/layouts/main.handlebars -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Restaurant List</title>
<link rel="stylesheet" href="/stylesheets/reset.css">
<link rel="shortcut icon" href="https://assets-lighthouse.s3.amazonaws.com/uploads/image/file/6227/restaurant-list-logo.png" type="image/x-icon">
<link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
<link rel="stylesheet" href="/stylesheets/bootstrap.css">
<link rel="stylesheet" href="/stylesheets/all.css">
</head>
載入所需的js在<body>
中
<body>
<!-- partial templates will replace the part of "body" here -->
{{> header}}
{{{ body }}}
{{> footer}}
<script src="/javascripts/jQuery.js"></script>
<script src="/javascripts/popper.js"></script>
<script src="/javascripts/bootstrap.js"></script>
</body>
</html>
設定header.handlebars footer.handlebars
將Header, Footer的html分別加入其handlebars。
設定index.handlebars
{{> searchBar}}
如同main一樣,解析/public/partials/searchBar.handlebars
時,透過>
來指定檔案路徑解析searchBar.handlebars。
- 第10、36行是利用
{{#each}}
完成迴圈動態資料 {{#each}}
內使用this
作為該物件。
<!-- search bar -->
{{> searchBar}}
<!-- restaurant list -->
<div class="container mt-5">
<div class="row">
<div class="col-md-10 col-12 mx-auto">
{{!-- change sorting list columns to rows--}}
<div class="row row-cols-1 row-cols-md-3">
{{!-- foreach loop for dynamic data--}}
{{#each restaurants}}
<div class="text-secondary mb-2 px-1 restaurant">
<div class="card mb-3">
<div class="cardImage">
<img class="card-img-top" src="{{this.image}}" alt="{{this.name}}">
{{!-- Add favorite switch btn --}}
<btn class="favorite" type="button">
<i class="far fa-heart fa-lg regular"></i>
<i class="fas fa-heart fa-lg solid"></i>
</btn>
</div>
<a href="/restaurants/{{this.id}}" class="card-body p-3">
<h6 class="card-title mb-1">{{this.name}}</h6>
<div class="restaurant-category mb-1">
<i class="fas fa-utensils pr-2"></i> {{this.category}}
</div>
<span class="badge badge-pill badge-danger font-weight-normal">
{{this.rating}}
<i class="fas fa-star fa-xs"></i>
</span>
</a>
</div>
</div>
{{/each}}
</div>
</div>
</div>
</div>
設定show.handlebars
{{restaurant.name}}
中的restaurant是透過index.handlebars抓取restaurant的物件。
<h1 class="mb-1 restaurant-show-title">{{restaurant.name}}</h1>
<div class="container">
<div class="row">
<div class="col-12 col-md-10 mx-auto">
<p class="mb-1">
<span class="text-secondary">
<i class="fas fa-utensils pr-2"></i>
類別:
</span>
{{restaurant.category}}
</p>
<p class="mb-1">
<span class="text-secondary">
<i class="fas fa-map-marker-alt pr-2"></i>
地址:
</span>
{{restaurant.location}}
<a href="{{restaurant.google_map}}" class="text-secondary" target="_blank">
<i class="fas fa-location-arrow pr-2 fa-xs"></i>
</a>
</p>
<p class="mb-1">
<span class="text-secondary">
<i class="fas fa-mobile-alt pr-2"></i>
電話:
</span>
{{restaurant.phone}}
</p>
<p class="mb-5">
{{restaurant.description}}
</p>
<img class="rounded mx-auto d-block mb-4 w-100" src="{{restaurant.image}}" alt="{{restaurant.name}}" style="max-width: 600px;">
</div>
</div>
</div>
監聽Web(app.js)
最後使用listen
方法來監聽Web。
// Listen the server when it started
app.listen(port, () => {
console.log(`Express is listening on localhost:${port}`)
})
測試專案
打開Terminal後,確認當前路徑為專案資料夾下後輸入nodemon app.js
,會出現監聽的模式,這時候可以打開瀏覽器輸入 http://localhost:3000/ 測試結果囉!
#測試專案
nodemon app.js
總結
第一次使用Express及Handlebars發現兩樣的寫法都還蠻直覺的,但是Handlebars需要一點時間去適應它的寫法。餐廳清單只是初版的進行,最後還會依序增加功能,如:會員登入、我的最愛、留言等,會在其他篇中繼續更新,並網站重構,使其功能更加完整。
最後附上GitHub連結
Contribute
感謝Alpha Camp提供此次專案素材及資源