---
# System prepended metadata

title: Node.js 快速入門
tags: [Web]

---


# Node.js 快速入門

:::warning
- 本篇筆記不包含 Javascript 基礎語法
    - 只有簡短的介紹 Javascript 模組相關的語法
- 關於 Javascript 語法，可以參考
    - [W3 Schools - JavaScript Tutorial](https://www.w3schools.com/js/)
    - [JS 語法簡介](/@PinJhih/js_basic)
:::

- Node.js 是一個 **伺服器端** 的 **Javascript 執行環境**
    - 安裝 Node.js 後，可以直接在電腦上執行 JS 程式碼
        - **不需要透過瀏覽器執行**、程式的功能**不被侷限於瀏覽器中**
        - 可以存取瀏覽器以外的系統資源，例如**檔案**、**資料庫**和**其他軟體**
    - 透過 Node.js，可以開發、架設許多 Web 應用程式
    - 搭配一些套件，可以用 JS 開發電腦上的應用程式

- 除了執行環境，Node.js 中還包含許多其他的開發資源，包含 ...
    - 豐富的 **內建函式庫和套件**
    - **Node.js 套件管理工具 - NPM**
        - Node Package Manager: Node.js 預設的套件管理工具
        - 用來管理 Node.js 專案，以及專案中使用到的所有套件
    - 大量的 **第三方套件**
        - 公開於官方的網站上，可以透過 NPM 下載、安裝

## 安裝與使用

### 安裝 Node.js

**Windows 及 Mac**
- 在 [**官方下載頁面**](https://nodejs.org/zh-tw/download) 選擇對應你電腦作業系統的版本
- 執行安裝程式後即可使用
    ![](https://hackmd.io/_uploads/BJNchp0an.png)

**Linux**
- Debian, Ubuntu ...
  ```
  sudo apt update
  sudo apt install nodejs
  ```

使用其他 Linux 發行版或作業系統，可以參考官方文件 [Installing Node.js via package manager](https://nodejs.org/en/download/package-manager)

:::success
**確認安裝結果**
- 打開任意的終端機 (Windows 上的 PowerShell, cmd ...)，執行以下指令，可以確認是否安裝成功
  ```
  node -v
  ```
- 若安裝成功，會顯示在你的電腦上目前安裝的 Node.js 的版本
    - 舉例來說，如果安裝的版本是 18.17.1，會顯示
      ```
      v18.17.1
      ```
    - 只要顯示類似的內容就表示安裝成功
:::

### 使用 Node.js

- 進入 Node.js 環境
    - 在終端機輸入 `node` 指令，可以打開互動式的執行環境
    - 在此環境下，可以執行輸入並執行 JS 程式碼，而不需要先寫好程式碼檔案
    ```
    node
    ```
- 執行 *.js* 檔案
    - 在 `node` 指令後加上要執行的 JS 程式碼檔案
    ```
    node <path_of_your_script>
    ```
    

完整使用方式可以參考[官方文件](https://nodejs.org/api/synopsis.html#usage)

### 在 VS Code 中使用 Node.js

- VS Code 本身就支援 Javascript 語法提示，不需要另外安裝 JS 的套件
- 如果想要不下指令、直接透過 UI 介面執行程式碼檔案，可以安裝擴充套件 **Code Runner**
    ![](https://hackmd.io/_uploads/HJmGCb06h.png)

    - 安裝 Code Runner 後，點擊右上角 "Run Code" 的按鈕，就可以執行程式碼檔案
    ![](https://hackmd.io/_uploads/H1SdRb0a3.png)

## Node.js 中的 Module

:::success
**Module** (模組)
- 一個模組是 **一段程式碼**，且具有以下特性的
- 內容通常有高度的 **相關性**
    - 一個模組的功能通常 **只和一個目的有關**
        - 比如: 用來處理檔案的模組、處理字串的模組、進行網路傳輸的模組
- 經過適當的**封裝** (encapsulated)
    - 可以被其他人或其他程式碼檔案，直接引入、使用
- 可以是 **一個 Javascript 程式碼檔案** 或 **一個包含多個檔案的資料夾**
- 通常一個模組**只會在它所屬的專案中使用**
    - 一個專案的程式碼可以被分開寫成多個模組
    - 專案 A 不會使用專案 B 的模組
<!--         - 從程式碼的角度是可以的
        - 但是從專案結構和慣例上來說，這是很糟糕的作法 -->
:::

:::warning
**Package** (套件)
- 一個 **資料夾**，通常 **包含多個模組**
- 此資料夾必須是一個 **Node.js 專案**
    - 需要有 NPM 的專案設定檔 *packages.json*
- 當一個專案成為 package，它就 **可以被其他專案使用**
    - 專案 A 是一個 package，提供一些檔案處理的模組
    - 專案 B 是一個應用程式，B 可以透過 NPM 安裝專案 A，並使用專案 A 提供的模組
- 詳細內容可以參考
    - [NPM 官方文件](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry)
    - [這篇文章](https://www.freecodecamp.org/news/how-to-create-and-publish-your-first-npm-package/)
:::

:::info
**Module Specifications**
- 建立 JS 模組的標準 (格式)
- 有 **CommonJS** 和 **ES** 兩種常見的標準
- Node.js 預設使用 **CommonJS**，所以本篇以 **CommonJS** 為主
- 詳細差異可以參考 [CommonJS vs ES](https://pjchender.dev/nodejs/node-module-system/)
:::

關於模組、套件的詳細內容，可以參考 [**官方文件**](https://nodejs.org/api/packages.html)

### 建立 Module (單檔案)

- 在程式碼中，一個 Module 會以 **物件** 的形式存在
- 在 CommonJS 標準中，修改 **`module.exports`** 屬性可以把某個物件 **匯出**
    - 等號右邊的物件就是代表這個模組的物件
        ```javascript
        module.exports = obj_of_module;
        ```
    - 把上面模組稱為 module A，另一個模組稱為 module B
        - 當 module B 引入(使用了) module A，module A 會拿到一個和 `obj_of_module` 一樣的物件
- 只要是 **物件就可以匯出成一個模組**，所以可以有多種不同的寫法
    - 關於 JS 的物件，可以參考
        - [這篇筆記](https://hackmd.io/1GUTx2PvQeOmXHBaRajpgw?view#Classes-amp-Objects)
        - [這篇文件](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Objects/Basics)

:::success
下面以建立 **一個提供數學計算的 module** 為例子
- 開啟一個檔案，命名為 *calculator.js*
    ```javascript
    // calculator.js
    function add(a, b) {
        return a + b;
    }

    function product(a, b) {
        return a * b;
    }

    const pi = 3.14159265;
    const e = 2.71828183;

    // 將以上兩個 function 匯出
    module.exports = { add, product, pi, e };
    // 要注意不要在 function 名稱後面加上 ()
    // 因為 add() 是這個 function 的執行結果
    // 而不是 function 本人
    ```
:::

:::info
- 其他等效的寫法
    ```javascript
    // 前略 ...

    var myModule = { add, product };
    module.export = myModule;
    ```
    ```javascript
    var myModule = {
      add: function (a, b) {
        return a + b;
      },
      product: function (a, b) {
        return a * b;
      },
      pi: 3.14159265,
      e: 2.71828183,
    };

    module.exports = myModule;
    ```
    ```javascript
    var myModule = {};

    myModule.add = function (a, b) {
      return a + b;
    };

    myModule.product = function (a, b) {
      return a * b;
    };

    myModule.pi = 3.14159265;
    myModule.e = 2.71828183;

    module.exports = myModule;
    ```
    
> 還有很多不同的寫法，只要符合 JS 物件的語法即可
:::

### 使用 Modules

- 呼叫 `require()`，可以在目前的檔案中引入其他模組
    - 參數是模組的檔案路徑
    - `require()` 會回傳其他模組匯出的物件
    - 通常會用一個變數把這個物件儲存起來
    ```js
    var module = require("path/to/the/module");
    ```

:::success
以剛剛建立的 *calculator* 模組為例
- 先建立一個新的檔案 *index.js*，檔案放在相同的資料夾中，並把要執行的程式寫在裡面
    ```javascript
    // index.js

    // 變數可以不用和模組名稱相同
    // "./calculator" 是相對路徑 表示目前目錄底下的 calculator 模組
    var cal = require("./calculator");
    
    // 引入 .js 檔，.js 可以省略
    // 所以上面那行也可以寫成 var cal = require("./calculator.js");

    // 呼叫 calculator 模組的 add
    console.log(cal.add(1, 2));
    ```
    **Output**
    ```
    3
    ```
:::

:::info
可以用 `console.log()` 把 `require()` 拿到的物件印出來
- 有時候物件內容較多，只會顯示 `[object Object]`
- 可以搭配 `JSON.stringify()` 把物件完整的印出來

```javascript
// index.js
var cal = require("./calculator");

console.log(cal);
// 或是 console.log(JSON.stringify(cal));
```
- **Output**
    ```json
    {
      add: [Function (anonymous)],
      product: [Function (anonymous)],
      pi: 3.14159265,
      e: 2.71828183
    }
    ```
    - 結構和 calculator.js 中，匯出的物件相同
:::

### 建立 Module (多檔案)

- 一個資料夾中的多個檔案 (單檔案的模組)，可以透過一些設定集結成一個模組
    - 在資料夾中建立 *index.js* 檔案
    - 在該檔案中匯出的物件，是代表整個資料夾物件
    - 只要 `require()` 資料夾名稱，就可以拿到 *index.js* 匯出的物件
- 其他模組只要一行 `require()` 就可以使用整個資料夾中的所有模組

:::success
以剛才的例子，我們可以把 *calculator* 細分成 *operation* 和 *constant* 兩個小模組
- 先建立一個資料夾 *calculator*，在裡面建立三個檔案 *operation.js*, *constant.js* 和
    - 檔案結構如下
        ```
        .
        └── calculator/
            ├── constant.js
            ├── operation.js
            └── index.js
        ```
- 在 *calculator/constant.js* 加入以下內容
    ```javascript
    // calculator/constant.js
    const pi = 3.14159265;
    const e = 2.71828183;

    module.exports = { pi, e };
    ```
- 在 *calculator/operation.js* 加入以下內容
    ```javascript
    // calculator/operation.js
    function add(a, b) {
      return a + b;
    }

    function product(a, b) {
      return a * b;
    }

    module.exports = { add, product };
    ```
- 在 *calculator/index.js* 中，引入兩個模組，整合成一個物件並匯出
    - 在 index.js 中，可以依自己的需求定義要匯出的物件，不一定要按照下面的方式
    - 下面舉例的是最簡單的方式
        - 使用這個模組時，會拿到一個物件，包含這個資料夾中的所有模組
        ```javascript
        // calculator/index.js
        var constant = require("./constant");
        var operation = require("./operation");

        module.exports = { constant, operation };
        ```
- 在 *calculator* 外面一層的資料夾建立一個 *index.js* 檔案，在這個檔案中編寫要執行的程式內容
    - 現在的檔案結構應該如下
        ```
        .
        ├── calculator/
        │   ├── constant.js
        │   ├── index.js
        │   └── operation.js
        └── index.js
        ```
- 在剛剛建立 *index.js* 中加入以下內容
    ```javascript
    // index.js

    // 引入 calculator 模組
    var calculator = require("./calculator");

    console.log("PI =", calculator.constant.pi);
    console.log("1 + 1 =", calculator.operation.add(1, 1));
    ```
    **Output**
    ```
    PI = 3.14159265
    1 + 1 = 2
    ```
:::

:::info
這邊一樣可以把 `require()` 回傳的物件印出來
```javascript
// index.js
var cal = require("./calculator");

console.log(cal);
```
- 在目前的情況下，calculator 模組是這樣的結構
    ```json
    {
      constant: { pi: 3.14159265, e: 2.71828183 },
      operation: { add: [Function: add], product: [Function: product] }
    }
    ```
    - 包含兩個屬性 `constant` 和 `operation`
    - `constant` 又有兩個屬性 `pi`, `e`
    - `operation` 又有兩個屬性 `add`, `product`
- 所以 `cal.operation.add` 就會對應 *calculator/operation.js* 中的 function `add`
:::

## NPM

- Node Package Manager
- 用來 **管理 Node.js 套件** 的軟體工具
    - 管理套件的 **相關資訊**
        - 名稱、作者、版本號碼、授權方式 ... 等
    - 管理 **Dependency**
        - 一個套件可以需要使用另一個套件
        - NPM 會自動安裝必要的其他套件
    - **安裝套件**
    - **發布套件**
        - 將自己的套件公布於 NPM 官網上供人下載
- 通常來說，**Node.js 專案** 會以 **NPM 套件** (專案) 的形式建構
    - 不需要 NPM 還是可以執行自己寫的程式碼、使用 Node.js 原生的模組
    - 但有了 NPM 才能正確的安裝、使用外部的套件

NPM 的完整使用方式可以參考 [NPM 官方文件]((https://docs.npmjs.com/))

:::info
**NPM 套件**
- 一個資料夾中只要有 ***package.json* 檔案**，這個資料夾就是一個 NPM 專案
- *package.json* 是 NPM **專案的設定黨**，包含所有和這個專案有關的資訊
- *package.json* 可以手動建立，下面是一個範例的內容
- 也可以透過 `npm` 指令建立
:::

### `npm` 指令

- 安裝 Node.js 時，就會一起安裝好 NPM，NPM 的指令是 `npm`
- 在終端機執行 `npm` 可以拿到使用簡介
    ```bash
    npm
    ```
    **Output**
    ```
    npm <command>

    Usage:

    npm install        install all the dependencies in your project
    npm install <foo>  add the <foo> dependency to your project
    npm test           run this project's tests
    npm run <foo>      run the script named <foo>
    npm <command> -h   quick help on <command>
    npm -l             display usage info for all commands
    npm help <term>    search for help on <term> (in a browser)
    npm help npm       more involved overview (in a browser)

    All commands:

    ...
    ```
    - 完整的 `npm` 指令使用方式，可以參考 [官方文件 - CLI Commands](https://docs.npmjs.com/cli/v9/commands)

### 建立 NPM Project

- 執行 `npm init` 可以自動建立標準格式的 *package.json* 檔案
    ```
    npm init
    ```
    - 過程中，`npm` 會一項一項詢問有關專案的資訊
        - 這些資訊可以在之後修改 (直接修改 *package.json*)
        - 這些資訊和程式碼的執行無關，依照自己的需求和喜好設定即可 (也可以全部使用預設數值)
    - 在每一個詢問的項目中
        - 如果要使用預設的數值，不要打任何文字直接按 enter，進入下一項設定
        - 如果要使用自己的數值，直接輸入你要設定的資訊，按 enter 進入下一項設定
- 詳細使用方式可以參考 [這篇文章](https://ithelp.ithome.com.tw/articles/10191682)


:::success
成功後，NPM 就會在目前的資料夾中建立 *package.json* 檔案
:::

### 安裝 Packages

- **Local install**
    - 這個情況下，套件只會 **安裝在目前的專案中**
        - 套件的相關檔案會被放在 ***node_modules* 資料夾**
        - 其他專案如果也要用相同的套件，就需要在那個專案中也安裝一次
    - 套件的相關訊息會被記錄在 *package.json* 中
    - 一般的 **函式庫** (library) 套件通常用 local install
        - 指要在自己的程式碼中呼叫、使用的套件
    ```bash
    npm install <package_name>
    ```

- **Global install**
    - 套件會安裝在 NPM 在你的系統中的目錄
    - 在電腦上，**任何的專案或目錄都可以使用這個套件**
    - 通常用來安裝一些 **工具類** 的套件
        - 指一些用 Node.js 開發的程式
        - 因為應用程式應該要能在電腦上的任何位置使用，所以才選用 global install
    ```bash
    npm install -g <package_name>
    ```

- **Install Dependencies**
    - 因為 local install 之後，外部套件的檔案會被放在專案的資料夾中
        - 會讓專案的總檔案大小變大
    - 所以在保存、傳輸或是 **初始化專案** 原始碼時，通常不會包含 *node_modules*
        - 也就是專案中沒有 *node_modules* (所以也不會有外部套件的檔案)
        - 但是專案中會用到的套件都記錄在 *package.json*
    - 這種情況通常發生在
        - 從 Github 或其他儲存庫 (repository) **下載專案程式碼** 時 (通常 *node_modules* 不會放上儲存庫)
        - 透過 **專案產生器** 產生初始專案時 (產生器通常也不會包含其他套件的程式碼)
    - 執行 `npm install`，就會根據 *package.json* 中的設定，自動安裝所有必要的套件
    ```
    npm install
    ```

- 更多內容可以參考
    - [官方文件](https://docs.npmjs.com/cli/v8/commands/npm-install)
    - [這篇文章](https://linyencheng.github.io/2022/09/27/relationships-between-frontend-and-backend/tool-npm/#npm-install-%E5%AE%89%E8%A3%9D-node-module-%E7%9A%84%E8%AA%9E%E6%B3%95)

:::info
- 使用 `npm` 安裝套件，其實不需要先執行 `npm init`
    - 執行 `npm install <package_name>` 時，就會建立一個沒有專案資訊的 *package.json*
    - *package.json* 會只有 dependency 的相關資訊
:::

### 移除 Packages

使用方式和安裝時類似，只要把 `install` 改成 `uninstall` 就可以了

```
npm uninstall <package_name>
```

### package.json

筆記待補

- 可以先參考
    - [NPM 官方文件](https://docs.npmjs.com/cli/v9/configuring-npm/package-json)
    - [Node.js 官方文件](https://nodejs.org/api/packages.html#nodejs-packagejson-field-definitions)

### 自訂指令

筆記待補

- 可以先參考
    - [官方文件](https://docs.npmjs.com/cli/v9/using-npm/scripts)
    - [這篇文章](http://jamestw.logdown.com/posts/1378697-egghead-how-to-use-npm-scripts-as-your-build-tool)
