# 後端basic - Express [Node.js Express 結構分析](https://ithelp.ithome.com.tw/articles/10222345) ![](https://i.imgur.com/QreZl15.png) 開始使用Express in project: 在目標資料夾使用`npm init`並用`npm install express`安裝 * Tools: Templating URL Mapping/Routing User Input Processing [Installation guides for common application development tools](https://treehouse.github.io/installation-guides/) | [解決: 因為這個系統上已停用指令碼執行,所以無法載入...](https://israynotarray.com/other/20200510/1067127387/) #### Server A **program** runs on a remote computer, wait for **HTTP request** from client(使用者輸入網址), then bring action to putting together a **response**. 通常會先在local端建server測試app,測試完成後再把app [Deploy](https://teamtreehouse.com/library/deploy-a-node-application-to-heroku) 到remote server. #### Route (end point) 對client端來說: Route是URLs 對server端來說: Route是**command**,會執行特定**function** which sends **response** back to client ## 開始使用express [Express doc - API reference](https://expressjs.com/en/4x/api.html) | 若code有更改,server必須重開才會更新,此時可用[nodemon](https://www.npmjs.com/package/nodemon)來自動化此步驟 ``` const express = require('express'); const app = express(); app.get('/', (request, response) => {...}); //參數1-PATH:'/'是root route, 參數2-callback ``` * `app.listen(port, callback)`method: 參數1 tell server which port to serve the app on ## Pug Templating #### template (views) ![](https://i.imgur.com/WPyXYFJ.png) templates are a special type of file that have their own syntax and language, **stored on server**, and act as some kind of **form letter** for your HTML. The result is a **full HTML page** sent to the client. * Rendering the template: provides the **basic HTML** for app and serves it to the users. * vary output to provide customized **responses**. * JS templatimg engine languages: **[Pug](https://pugjs.org/api/getting-started.html)**(formerly Jade), **[Handlebars](https://handlebarsjs.com/)**, **[EJS](https://ejs.co/)** ![](https://i.imgur.com/BdPOYvD.png) [pug加attrubutes](https://pugjs.org/language/attributes.html) #### start to use pug 1. Downloading Pug with npm 2. Update code with `app.set()` method in app to use Pug ``` app.set('view engine', 'pug'); //參數2:要用哪個engine ``` 3. Create templates and folder for templates By default, Express會指向root裡面的'views'資料夾,我們會把Template檔案(e.g. `index.pug`)放在裡面。若要更改路徑參考[app.set()](https://expressjs.com/en/guide/using-template-engines.html) 4. Render templates with response.render() ``` res.render('index'); //Pug會自己抓資料夾裡面的檔案名來render ``` ![](https://i.imgur.com/gGA6fEC.png) 可用dev tool來查看template的內容,並整理排版![](https://i.imgur.com/bCkgtjL.png) 5. templates.pug檔案常用開頭: ``` doctype html html(lang="en") head title Your Web Page body ``` * [Using Logic in Pug](https://orandigo.github.io/blog/2020/12/27/20201227-pug-note/) 可做if...else判斷,loop等 #### interpolation (template literals of Pug) ``` h1 My name is #{name} //<h1>My name is Andrew</h1> ``` attributes(title, id, class...)不接受interpolation,所以要這樣寫: ``` h1(title='My name is '+ name) #{name} // <h1 title='My name is Andrew'>Andrew</h1> ``` #### [Template Inheritance](https://pugjs.org/language/inheritance.html) 可以寫一個layout.pug包含所有template會共用的部分,並讓child.pug繼承parent.pug來共用layout * 方法1:block content ``` //parent template: layout.pug block content ------ //child template extends layout.pug //延伸的template路徑,parent.pug block content //讓child.pug輸入內容到content block section#content //這些內容會被放入parent.pug的content block ``` *方法2:[Include](https://pugjs.org/language/includes.html) 常用在header跟footer ``` //parent template: layout.pug include includes/header.pug //views裡創建includes資料夾,並連結child檔案路徑 ------ //child template header //直接寫內容 h1 Assignment 2 ``` #### [HTML to Pug](https://html2jade.org/) ## Request object & Response object in Pug ### Request object Gives us access to data the client has sent to the server, such as values of fields from a form submission. #### making a [POST](https://expressjs.com/en/4x/api.html#app.post.method) request using `form` tag: send data to the server ``` form(action='/url', method='post') //參數1:提供data到哪個path(空白會提供給當前頁面), 參數2:request method label Please enter a Number: input(type='number', name='inputValue') //給user的輸入欄位 button(type='submit') Submit //submit按鈕 ``` #### [Request](https://expressjs.com/en/4x/api.html#req) Object in Pug * `req.body`: 注意body property, this is where our ***form response* will end up**. By default, it is **undefined** * **Middleware**: some **code fits in the middle** of a *request* or *response*, it dose something with the *incoming data*(HTTP request), and hands the result of to the app. We use middleware **to put the form response into the body**. (e.g.`express.json()` or `express.urlencoded()`) ``` app.use(express.urlencoded({ extended: false})); //不要問為甚麼,之後user輸入的資料就會進到req.body了 ``` ![](https://i.imgur.com/ahywpw4.png) * `express.urlencoded()`在做這件事: ![](https://i.imgur.com/s8sQ25y.png) * 三個獲取參數的method: `req.params` `req.body` `req.query` ### [Response](https://expressjs.com/en/4x/api.html#res) object Packaging a response to be sent back to the client. #### res.send() ``` res.send('<h1>Hello World!</h1>') ``` #### [res.render()](https://expressjs.com/en/4x/api.html#res.render) Method: Turn templates into HTML ``` res.render(view [, locals] [, callback]) //[]代表optional ``` 參數1:view檔案名(不用加附檔名.pug) 參數2:Locals-{var:value} for view to have access to when being rendered. ### Locals Locals是一個object(e.g.`{prompt: 'Who are you?'}`,`{num: request.body.num}`) 也可使用`res.locals`,以下兩段程式碼等效 ``` res.render('card', {prompt:'Who is buried in Grant's tomg?}); //在.pug檔裡使用prompt會輸出值'Who is buried in Grant's tomg? ``` ![](https://i.imgur.com/Dn346qX.png) * 兩種把res.locals傳入template.pug的方法 ``` h1= prompt //若var沒定義,則為undefined, h1值為空白 h1 The hint is #{name} //類似template literals的用法 ``` ![](https://i.imgur.com/P4Tl1L8.png) #### GET POST cycle express裡設定GET跟POST request(num的值由POST form裡的.inputValue定義) ![](https://i.imgur.com/CcNBgeT.png) Pug裡寫if num有被定義則GET,else則render POST(屬性name:inputValut) ![](https://i.imgur.com/G6asYRV.png) #### res.json() For client who don't want all the persentation of HTML CSS, they just want data. (e.g. build REST API) ``` res.json(req.body); //return {"username":"Andrew"} ``` ## Modular Routes ### Cookies Cookie store **state** data on the client. (Further Resources for Storing State: keywords - Local Storage, SQL and Node.js with Sequelize, Mongo) 1. [`res.cookie()`](https://expressjs.com/en/4x/api.html#res.cookie): Send a cookie to the broswer after *submit the form*, then the browser will automatically send this cookie with *every request it makes*. ``` //res.cookie(name, value [, options]) res.cookie('username', req.body.username); //參數1:name, 參數2:從form取出的值 ``` 但此時server isn't reading回傳的cookie, to read the cookie from the request, 在request object裡需要有 cookie's property, 此時就需要cookie-parser這個Middleware. 2. [`cookie-parser`](https://github.com/expressjs/cookie-parser): 可以讓我們讀取form裡的data * npm install cookie parser * config and use cookieParser in your app ``` const cookieParser = require('body-parser'); app.use(cookieParser()); ``` 3. [`res.cookies()`](https://expressjs.com/en/4x/api.html#req.cookies): 讀取步驟1 form submit的值. When using *cookie-parser* middleware, this property is an **object** that contains *cookies sent by the request*. If the request contains no cookies, it defaults to `{}`. ![](https://i.imgur.com/nuPYG13.png) ### redirect 當Client端request一個 *受保護的*(需login) 或*輸入錯誤的*(Google買下的類似domain) 或*被移動的* URL, Server通常會回覆一個包含redirect URL的response, Client收到後會立刻make a 2nd request去這個new URL. HTTP Redirects require a browser or client to make a second request and **don't require action from the user** to follow the new URL being redirected to. * `res.redirect([status,] path)` 利用cookie state來redirect: 若cookie state裡有username,首頁會歡迎{[name](https://ui.dev/shorthand-properties)}; 若無,redirect到inputForm讓使用者輸入username ![](https://i.imgur.com/MrrnDjA.png) * `res.clearCookie()` 首頁清除cookie後redirect到inputForm ![](https://i.imgur.com/nKZXMlK.png) ![](https://i.imgur.com/5ZmaihV.png) ## [Middleware](https://expressjs.com/en/guide/writing-middleware.html) 讀取req跟res的需求,處理成回覆給使用者的res * Basic form of Express middleware ``` (req, res, next) => { // do something next(); } ``` * express使用middleware ``` app.use((req, res, next) => {}); //for every request app.use('/users', (req, res, next) => {}); //run for特定route ``` `.use`可以換成`.get`,單純處理get request * Sequence: 會按順序由上往下執行,也可用`,`增加多個function ``` app.use((req, res, next) => { //func1 console.log('one'); next(); }, (req, res, next) => { //func2 console.log('two'); next(); }); app.use((req, res, next) => { //func3 console.log('three'); next(); }); //one two three ``` * pass info ``` app.use((req, res, next) => { req.message = 'I made it!'; next(); }); app.use((req, res, next) => { console.log(req.message); next(); }); //I made it! ``` #### The `next` function * `next()`;宣告一個middleware-function結束了,可以執行下一個 * 要結束一個middleware function,要馬`next()`,要馬完成一個response (e.g.`.render()`,`.send()`),不然app會一直loading * third party middleware: these func are being called as Express sets up the server and returns middleware functions.([Closures的概念](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)) ![](https://i.imgur.com/vBxPimZ.png) ### Handling Errors in an Express App By Passing an object as a parameter to `next()`來抓app裡的錯誤 ``` app.use((req, res, next)=>{ const err = new Error('Oh noes!'); //製作一個方便查找的簡短Error message err.status = 500; //這裡的.status是普通的object property, 500代表general error next(err); }); ``` ![](https://i.imgur.com/cxP54V3.png) This output comes from Express's built in error handler.紅線為錯誤發生位置 #### Error Handling Middleware * Error Middleware ``` (err, req, res, next) => {} //Express會把Error object pass給第一個有4個parameter的Middleware(類似request.on("error", () =>{})的概念) ``` ``` app.use((err, req, res, next)=>{ res.local.error = err; //把local.error property設成err res.status(err.status); //抓取status code給browser res.render('error', err); //參數1:error template, 參數2:error object }); ``` * `res.status()`method takes the number of the code. ![](https://i.imgur.com/P5bSBls.png) #### Handling 404 Errors A 404 signals the requested route dosen't exists. Express's native handler會傳送預設的404 plain text給client端 如何catch the request before他跑到最後,並傳送客製化404頁面 ![](https://i.imgur.com/eTO7enC.png) ``` //做一個'error製造Middleware'放在Error Middleware之前,Express跑到這,會使用這個製造機 app.use((req, res, next)=>{ const err = new Error('Not Found'); err.status = 404; next(err); }); ``` ## Parameters, Query Strings, and Modularizing Routes ### Modular Routes ![](https://i.imgur.com/ewaJxLa.png) 通常業界會把route依功能模組拆分成數個檔案,放在route資料夾 ![](https://i.imgur.com/smqhgFe.png) ``` //index.js裡的config const express = require('express'); const router = express.Router(); ====== router.get('/'......); router.post('/inputForm'......); ====== module.exports = router; ``` ``` //cards.js裡的config const express = require('express'); const router = express.Router(); ====== router.get('/'......); //因為在app.js裡app.use('/cards', cardRoutes); 所以在cards.js裡的route都會多一個/cards ====== module.exports = router; ``` ### Using Data and Route Parameters 以下程式碼使用{[ES6 Shorthand Properties](https://ui.dev/shorthand-properties)} + 解構賦值 ![](https://i.imgur.com/HgmLbNb.png) * Route Parameters `/:` ``` // '/:'的':'後面的值會被當成param的name,以id示範 router.get('/:id',(req, res)=>{}); ``` * [`req.params`](https://expressjs.com/en/api.html#req.params) object 網頁網址會變`/url/id` * [`req.query`](https://expressjs.com/en/api.html#req.query) object 用query string的方法取值 網頁網址會變`/url?key1=value1&key2=value2` ### 隨機card的.pug ![](https://i.imgur.com/PFe72dm.png) ## Serving Static Assets Static Assets: 靜態網頁內容(CSS, client-side JS),存放在web server資料夾內的完整檔案,會直接交由browser(Client端)執行.(Express無法讀取Client端的js) #### Setup 1.先在project裡建立一個public(業界慣用)資料夾,裡面存放.css檔案 #### `express.static()` method 2.設一個route連到public資料夾 ``` app.use('/static', express.static('public')); ``` ![](https://i.imgur.com/MlK355W.png) 3.若希望所有頁面都使用此stylesheet, 在layout.pug裡連結此檔案(css會放在`<head>`tag裡) ![](https://i.imgur.com/uvYXGPw.png) 4.調整template