Node.js 學習筆記

Amber Lin
18 min readSep 15, 2023

--

Why Node.js? 為何選擇Node.js

=> 1. This is based on Javascript to program the back-end development or server-side render. 基於JS 語言做網頁後端開發或SSR

2. It allows us to run Javascript outside the browser( Chrome, Firefox, Edge, etc…) 允許在瀏覽器以外運行程式碼

Node.js 內建(built-in module)的Core Module

Made from ChatGPT

Part 1. Express.js 開 API (Ref: 六角學院 - NODE.JS 開 API 不求人)

Express.js官網文件

資料夾架構:
.
├── app.js //程式進入點: 當你在routes資料夾新增一個js檔時, 要記得到此添加設定
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.pug
├── index.pug
└── layout.pug
app.js檔案:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var apiRouter = require('./routes/api'); //將一個名為api.js的檔案加到 進入點內

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(express.json({ strict: false }));
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));


app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/api', apiRouter); //app.use(新路由, 引入檔案位置)

// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});



module.exports = app;
api.js檔案內容:

var express = require('express');
var router = express.Router();


const data=[
{
"id": 1,
"title": "product1"
},
{
"id": 2,
"title": "product1"
},
{
"id": 3,
"title": "product2"
},
{
"id": 4,
"title": "product2"
},
{
"id": 5,
"title": "product2"
},
{
"id": 6,
"title": "product2"
}
]

//路由表

//localhost:3000/api/
router.get('/', function(req, res, next) {
res.render('products', { title: 'Products' });
});

/* GET method for products page. */
router.get('/products', function(req, res, next) {
res.status(200);
res.send({ success: true, data });
res.end();
});

/* POST method for products page. */
router.post('/products', function(req, res) {

const product=req.body;
console.log(product);
data.push({
id:data.length+1,
...product,
});
res.status(200);
res.send(
{ success: true,
data,
}
);
res.end();
});

//Delete method for products page /:id 表示以id作為動態的參數
router.delete('/products/:id', function (req, res) {
const id = req.params.id;
console.log(id);
res.status(201);
data.forEach((item, key) => {
if (item.id == id) { //不用比對型別, 所以是兩個等號
data.splice(key, 1);
console.log('刪除成功');
console.log(item,key);
}
});
res.send(
{ success: true,
data,
}
);
res.end();
});

module.exports = router;

Part 2. Express.js + MongoDB

From Hiskio — 網頁開發全端攻略|零程式基礎也適用 課程

在Terminal 執行node.js 與 Browser執行node.js 兩者運作方式不同

At Terminal: Node.js 有個Module Wrapper的概念,

The module wrapper:

(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here, scoped to variables in module function
let myname="Nancy";
let sayHi=()=>{
console.log('Hello, my name is'+ myname)
}
sayHi();
});

基於這個運作方式, 我們可以得到完整的檔案位置及名稱

console.log(__filename);
// Prints: /Users/mjr/example.js
console.log(__dirname);
// Prints: /Users/mjr

What is the Module in Node.js?

You have gotten the built-in module after installing node.js, or you can build your own modules with multiple JS functions(self-made module), and get it from other people.

Module 是一個Object:
{
id: '.',
path: '/Users/XXX/Hiskio/FullEnd',
exports: {},
filename: '/Users/XXX/Hiskio/FullEnd/try2.js',
loaded: false,
children: [],
paths: [
'/Users/XXX/Hiskio/FullEnd/node_modules',
'/Users/XXX/Hiskio/node_modules',
'/Users/XXX/node_modules',
'/Users/XXX/node_modules',
'/Users/XXX/node_modules',
'/Users/node_modules',
'/node_modules'
]
}

[ HTTP framework: Fastify ]

在 Fastify 應用程式中,通常情況下可以分為兩種情況:

註冊插件:

當你想要為整個 Fastify 應用程式添加全域功能或者功能模組時,例如身份驗證、資料庫連接、日誌記錄等。當你想要將功能模組添加到 Fastify 的生命週期鉤子中時,例如 onRequestpreHandleronResponse 等。

引入功能模組:當你想要將某個功能模組與特定的路由或路由組件相關聯時,例如路由處理函數、路由選項等。當你需要將模組的特定功能與 Fastify 應用程式的其他部分解耦時。

基本上,如果你希望功能在整個應用程式中可用,或者希望它與 Fastify 的生命週期鉤子一起工作,那麼應該註冊為插件。如果功能只與特定的路由或路由組件相關聯,則將其引入並直接使用即可。

專案進度: 能有登入取得token的功能、設定API是否需要驗證token、接收User上傳圖片並存到本地端

Full Stack
├─ backend
│ ├─ db-connector.js
│ ├─ package-lock.json
│ ├─ package.json
│ ├─ route.js
│ ├─ server.js
│ └─ user_upload
│ └─ bfa3a80d-627a-4de1-857a-b222da0b9654.jpg
└─ frontend
├─ .env
├─ index.html
├─ package-lock.json
├─ package.json
├─ public
│ ├─ favicon.ico
│ └─ vite.svg
├─ src
│ ├─ api
│ │ ├─ api.js
│ │ ├─ base.js
│ │ ├─ http.js
│ │ └─ index.js
│ ├─ App.vue
│ ├─ assets
│ │ ├─ images
│ │ │ └─ logo_line.png
│ │ └─ styles
│ │ ├─ App.scss
│ │ ├─ Global.css
│ │ ├─ Global.css.map
│ │ ├─ Global.scss
│ │ ├─ scss
│ │ │ ├─ Base
│ │ │ │ ├─ _font.scss
│ │ │ │ └─ _reset.scss
│ │ │ ├─ Layout
│ │ │ │ ├─ _color.scss
│ │ │ │ ├─ _main.scss
│ │ │ │ └─ _rwd.scss
│ │ │ ├─ Variation
│ │ │ │ ├─ _animate.scss
│ │ │ │ ├─ _color.scss
│ │ │ │ └─ _function.scss
│ │ │ └─ vendors
│ │ │ └─ _element-plus.scss
│ │ └─ style.css
│ ├─ components
│ │ ├─ table
│ │ │ └─ TableAction.vue
│ │ ├─ TableList.vue
│ │ └─ TestItem.vue
│ ├─ main.js
│ ├─ router
│ │ └─ index.js
│ ├─ store
│ │ └─ index.js
│ └─ views
│ ├─ Home.vue
│ ├─ MainPage.vue
│ └─ Transform.vue
└─ vite.config.js
[ server.js ]
// Require the framework and instantiate it
const fastify = require('fastify')({logger:true});
const cors = require('@fastify/cors');


// 註冊Middleware到所有路由
fastify.register(cors);
fastify.register(require('@fastify/jwt'), {
secret: 'token加密的密碼'
});

//fastify能支援 multipart/form-data 這種媒體類型
fastify.register(require('@fastify/multipart'), {
limits: {
fieldNameSize: 100, // Max field name size in bytes
fieldSize: 100, // Max field value size in bytes
fields: 10, // Max number of non-file fields
fileSize: 1000000, // For multipart forms, the max file size in bytes
files: 1, // Max number of file fields
headerPairs: 2000, // Max number of header key=>value pairs
parts: 1000 // For multipart forms, the max number of parts (fields + files)
}
});
fastify.register(require('./route'));



// 監聽端口
fastify.listen(3000, '0.0.0.0', (err, address) => {
if (err) {
fastify.log.error(err);
throw err;
}
});

[ route.js ]
async function routes(fastify, options) {
const fs = require("fs");
const path = require("path");
const { v4: uuidv4 } = require("uuid");
const dbConnector = require("./db-connect"); // 引入 dbConnector 插件
// 定義Middleware來驗證Bearer Token
// 假設這是你的使用者資訊,用戶名和密碼
const user = {
username: "user123",
password: "password123",
};

// 登入路由,接收用戶名和密碼,並返回 JWT Token
fastify.post("/login", (request, reply) => {
const { username, password } = request.body;
console.log("request.body:", username, password);
// 檢查用戶名和密碼是否正確,這裡僅作簡單的模擬
if (username === user.username && password === user.password) {
// 用戶名和密碼驗證成功,生成 JWT Token
const token = fastify.jwt.sign({ username: user.username });
reply.send({ token });
} else {
// 用戶名或密碼錯誤,返回 401 未授權
reply.code(401).send({ message: "Unauthorized" });
}
});
// Verify request with token
fastify.decorate("authenticate", async function (request, reply) {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
});
// 定義一個API路由
fastify.get("/", (request, reply) => {
return "No need to bring token";
});
fastify.get(
"/test",
{
onRequest: [fastify.authenticate],
},
(request, reply) => {
reply.send({ message: "Need to bring the token" });
}
);

fastify.route({
method: ["GET", "POST", "DELETE"],
url: "/api/data",
onRequest: [fastify.authenticate],
handler: async (req, reply) => {
reply.send({
message: "This route allows GET, POST, and DELETE methods",
});
},
});
//接收User上傳的圖片
fastify.route({
method: ["POST"],
url: "/api/img",
onRequest: [fastify.authenticate],
handler: async (req, reply) => {
try {
// const file = await req.raw.file; // 從 request 對象中獲取上傳的文件
const file = await req.file();
console.log("file:", file);
if (!file) {
throw new Error("No file uploaded");
}

// 檢查文件類型
if (!file.mimetype.startsWith("image/")) {
throw new Error("Uploaded file is not an image");
}
// 生成唯一文件名
const fileName = `${uuidv4()}${path.extname(file.filename)}`;

// 將文件保存到伺服器中
const uploadPath = path.join(__dirname, "user_upload", fileName);
await fs.promises.writeFile(uploadPath, file.file);

// 返回成功回應
reply.send({
message: "Image uploaded successfully",
imageUrl: `http://your-domain.com/user_upload/${fileName}`, // 將文件 URL 返回給客戶端
});
} catch (err) {
// 錯誤處理
reply.code(500).send({ error: err.message });
}
},
});
}

module.exports = routes;

[ db-connect.js ] 還在測試中
const fastifyPlugin = require('fastify-plugin')

// 定义一个异步函数 dbConnector,该函数接收两个参数:fastify 和 options
async function dbConnector (fastify, options) {
// 在 fastify 对象上注册 fastify-mongodb 插件
fastify.register(require('fastify-mongodb'), {
// 指定 MongoDB 数据库的 URL
url: 'mongodb://localhost:27017/test_database'
})
}

// 导出一个使用 fastify-plugin 包装过的插件
module.exports = fastifyPlugin(dbConnector)

[ Process Management Tool: pm2 ]

--

--

Amber Lin
Amber Lin

Written by Amber Lin

A Front-End Development with Vue3 framework

No responses yet