Learning From Youtube Channel: Traversy Media
Video: Express Crash Course (2024 Revamp)
Thank you.
Timestamps:
00:00:00 – Intro & Slides
00:00:53 – What is Express?
00:02:28 – Opinionated vs Unopinionated
00:04:10 – Prerequisities
00:05:18 – What we’ll cover
00:06:45 – Express Setup
00:09:00 – Basic Server
00:13:10 – –watch Flag & NPM Scripts
00:15:25 – res.sendFile() Method
00:18:00 – Static Web Server
00:19:48 – Working with JSON
00:22:35 – Postman Utility
00:23:45 – Environment Variables (.env)
00:26:30 – Request Params (req.params)
00:29:35 – Query Strings (req.query)
00:33:19 – Setting Status Codes
00:36:40 – Multiple responses
00:37:35 – Route Files
00:41:40 – Using ES Modules
00:43:47 – Request Body Data
00:47:53 – POST Request
00:50:23 – PUT Request
00:53:23 – DELETE Request
00:55:44 – Middleware
01:00:24 – Custom Error Handler
01:08:30 – Catch All Error Middleware
01:10:47 – Colors Package
01:14:17 – Using Controllers
01:20:45 – __dirname Workaround
01:24:29 – Making Requests From Frontend
01:30:03 – Submit Form to API
01:36:00 – EJS Template Engine Setup
01:41:15 – Pass Data to Views
01:42:50 – Pass and Loop Over Arrays
01:44:20 – Template Partials
Intro & Slides
What Is Express?
- Minimal and flexible web framework for Node.js
- Used for building server-side web applications and APIs
- The most popular framework for Node
- Simplifies the process of handling HTTP requests and responses
- Express is a very fast and unopinionated framework
Opinionated vs Unopinionated
Opinionated
- Suggested ways to do things
- Usually offer a lot of bells & whistles
- Strict folder structure
Laravel、django、Rails
Unopinionated
- Different ways to do the same thing
- Includes the bare necessities
- Structure folders how you want
Express、Flask、Sinatra
Prerequisites
- JavaScript fundamentals (Functions, loops, objects, classes, etc)
- HTTP Basics (Methods, status codes, etc)
- How JSON APIs work
- Basics understanding of Node.js
- NPM (Node Package Manager)
What we’ll cover
Covered In This Course
- Routing
- Request & Response
- Custom Middleware
- CRUD Operations
- Template Engines
- Error Handling
- 3rd Party NPM Packages
- Controllers
- Fetching From Frontend
- Environment Variables
Express Setup
- Express.js
- 建立 package.json 檔案 – npm init -y
- 安裝 Express 套件- npm i express
- 建立 .gitignore 檔案
// .gitignore
node_modules/
.env
Basic Server
- 建立進入點 server.js 檔案
- 終端機執行 node server.js
// server.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send({ message: 'Hello World' });
});
app.listen(8000, () => console.log(`Server is running on port 8000`));
–watch Flag & NPM Scripts
- 也可以選擇安裝 nodemon 套件
- 現在 Node.js 可以使用 –watch Flag
- 修改 package.json 檔案
- 修改 server.js 檔案
- 終端機執行 – npm run dev
// package.json
{
"name": "express_crash",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node server",
"dev": "node --watch server"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.19.2"
}
}
// server.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
app.get('/about', (req, res) => {
res.send('About');
});
app.listen(8000, () => console.log(`Server is running on port 8000`));
res.sendFile() Method
- 建立 public 資料夾來放置關於 HTML 相關檔案
- 在 public 資料夾內建立 index.html、about.html 檔案
- 修改 server.js 檔案
// public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome</title>
</head>
<body>
<h1>Welcome to the homepage</h1>
</body>
</html>
// public/about.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About</title>
</head>
<body>
<h1>Welcome to the about page</h1>
</body>
</html>
// server.js
const express = require('express');
const path = require('path');
const app = express();
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.get('/about', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'about.html'));
});
app.listen(8000, () => console.log(`Server is running on port 8000`));
Static Web Server
- 修改 server.js 檔案
setup static folder 只有 index.html 檔案能正常使用
// server.js
const express = require('express');
const path = require('path');
const app = express();
// setup static folder
app.use(express.static(path.join(__dirname, 'public')));
app.listen(8000, () => console.log(`Server is running on port 8000`));
Working with JSON
- 路徑: http://localhost:8000/api/posts
// server.js
const express = require('express');
const path = require('path');
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
app.get('/api/posts', (req, res) => {
res.json(posts);
});
app.listen(8000, () => console.log(`Server is running on port 8000`));
Postman Utility
- 使用 Postman 套件
- 我選擇使用 Hoppscotch
Environment Variables (.env)
- 建立 .env 檔案,PORT=8080、PORT=8000 前後的變化
- 修改 server.js 檔案
- 修改 package.json 檔案
// .env
PORT = 8000
// server.js
const express = require('express');
const path = require('path');
const port = process.env.PORT || 8000;
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
app.get('/api/posts', (req, res) => {
res.json(posts);
});
app.listen(port, () => console.log(`Server is running on port ${port}`));
// package.json
{
"name": "express_crash",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node server",
"dev": "node --watch --env-file=.env server"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.19.2"
}
}
Request Params (req.params)
- 修改 server.js 檔案
- 使用 API 測試工具
// server.js
const express = require('express');
const path = require('path');
const port = process.env.PORT || 8000;
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
app.get('/api/posts', (req, res) => {
res.json(posts);
});
// Get single post
app.get('/api/posts/:id', (req, res) => {
const id = parseInt(req.params.id);
res.json(posts.filter((post) => post.id === id));
});
app.listen(port, () => console.log(`Server is running on port ${port}`));
Query Strings (req.query)
- 查詢字串
http://localhost:8000/api/posts?limit=2
http://localhost:8000/api/posts?limit=2&sort=desc - 修改 server.js 檔案
- 使用 API 測試工具
// server.js
const express = require('express');
const path = require('path');
const port = process.env.PORT || 8000;
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
app.get('/api/posts', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
res.json(posts.slice(0, limit));
} else {
res.json(posts);
}
});
// Get single post
app.get('/api/posts/:id', (req, res) => {
const id = parseInt(req.params.id);
res.json(posts.filter((post) => post.id === id));
});
app.listen(port, () => console.log(`Server is running on port ${port}`));
Setting Status Codes
- 修改 server.js 檔案
- 使用 API 測試工具
http://localhost:8000/api/posts/1
http://localhost:8000/api/posts/2
http://localhost:8000/api/posts/200
// server.js
const express = require('express');
const path = require('path');
const port = process.env.PORT || 8000;
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
app.get('/api/posts', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
res.status(200).json(posts.slice(0, limit));
} else {
res.status(200).json(posts);
}
});
// Get single post
app.get('/api/posts/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
res.status(404).json({ msg: `A post with the id of ${id} was not found` });
} else {
res.status(200).json(post);
}
});
app.listen(port, () => console.log(`Server is running on port ${port}`));
Multiple responses
// server.js
const express = require('express');
const path = require('path');
const port = process.env.PORT || 8000;
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
app.get('/api/posts', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
app.get('/api/posts/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
app.listen(port, () => console.log(`Server is running on port ${port}`));
Route Files
- 建立 routes 資料夾
- 在 routes 資料夾裡面建立 posts.js 檔案
- 修改 server.js 檔案
// routes/posts.js
const express = require('express');
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
module.exports = router;
// server.js
const express = require('express');
const path = require('path');
const posts = require('./routes/posts');
const port = process.env.PORT || 8000;
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/api/posts', posts);
app.listen(port, () => console.log(`Server is running on port ${port}`));
Using ES Modules
- 修改 package.json 檔案,加入 “type”: “module”,
- 修改 server.js 檔案
- 修改 posts.js 檔案
// package.json
{
"name": "express_crash",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node server",
"dev": "node --watch --env-file=.env server"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.19.2"
}
}
// server.js
import express from 'express';
import path from 'path';
import posts from './routes/posts.js';
const port = process.env.PORT || 8000;
const app = express();
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/api/posts', posts);
app.listen(port, () => console.log(`Server is running on port ${port}`));
// posts.js
import express from 'express';
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
export default router;
Request Body Data
- 開啟新的 Postman Request
http://localhost:8000/api/posts
在 Body > raw 或者 x-www-form-urlencoded - 修改 server.js 檔案
- 修改 posts.js 檔案
- 使用 x-www-form-urlencoded 輸入 Key: title、Value: My Title
// server.js
import express from 'express';
import path from 'path';
import posts from './routes/posts.js';
const port = process.env.PORT || 8000;
const app = express();
// Body parser middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false}));
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/api/posts', posts);
app.listen(port, () => console.log(`Server is running on port ${port}`));
// posts.js
import express from 'express';
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
// Create new post
router.post('/', (req, res) => {
console.log(req.body);
res.status(201).json(posts);
});
export default router;
POST Request
- 修改 posts.js 檔案
- 在 API 測試工具輸入 Parameter 1: title、Value 1: Brads Post
// posts.js
import express from 'express';
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
// Create new post
router.post('/', (req, res) => {
const newPost = {
id: posts.length + 1,
title: req.body.title
};
if (!newPost.title) {
return res.status(400).json({ msg: 'Please include a title' });
}
posts.push(newPost);
res.status(201).json(posts);
});
export default router;
PUT Request
- 修改 posts.js 檔案
- 使用 API 測試工具、PUT 方法
http://localhost:8000/api/posts/2
Key: titl、Value: Updated Post - 可以搭配使用 MongoDB 資料庫
// posts.js
import express from 'express';
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
// Create new post
router.post('/', (req, res) => {
const newPost = {
id: posts.length + 1,
title: req.body.title
};
if (!newPost.title) {
return res.status(400).json({ msg: 'Please include a title' });
}
posts.push(newPost);
res.status(201).json(posts);
});
// Update Post
router.put('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
post.title = req.body.title;
res.status(200).json(posts);
});
export default router;
DELETE Request
- 修改 posts.js 檔案
- 使用 API 測試工具、DELETE 方法
http://localhost:8000/api/posts/1
// posts.js
import express from 'express';
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
// Create new post
router.post('/', (req, res) => {
const newPost = {
id: posts.length + 1,
title: req.body.title
};
if (!newPost.title) {
return res.status(400).json({ msg: 'Please include a title' });
}
posts.push(newPost);
res.status(201).json(posts);
});
// Update Post
router.put('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
post.title = req.body.title;
res.status(200).json(posts);
});
// Delete Post
router.delete('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
posts = posts.filter((post) => post.id !== id);
res.status(200).json(posts);
});
export default router;
Middleware
- 修改 posts.js 檔案
- 使用 API 測試工具、GET 方法
http://localhost:8000/api/posts - 建立 middleware 資料夾
- 在 middleware 資料夾裡面建立 logger.js 檔案
- 修改 server.js 檔案
- 使用 API 測試工具、GET 方法
http://localhost:8000/api/posts/1
http://localhost:8000/api/posts - 使用 API 測試工具、DELETE 方法
http://localhost:8000/api/posts/1
// posts.js
import express from 'express';
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
res.status(200).json(post);
});
// Create new post
router.post('/', (req, res) => {
const newPost = {
id: posts.length + 1,
title: req.body.title
};
if (!newPost.title) {
return res.status(400).json({ msg: 'Please include a title' });
}
posts.push(newPost);
res.status(201).json(posts);
});
// Update Post
router.put('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
post.title = req.body.title;
res.status(200).json(posts);
});
// Delete Post
router.delete('/:id', (req, res) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
return res
.status(404)
.json({ msg: `A post with the id of ${id} was not found` });
}
posts = posts.filter((post) => post.id !== id);
res.status(200).json(posts);
});
export default router;
// logger.js
const logger = (req, res, next) => {
console.log(
`${req.method} ${req.protocol}://${req.get('host')}${req.originalUrl}`
);
next();
};
export default logger;
// server.js
import express from 'express';
import path from 'path';
import posts from './routes/posts.js';
import logger from './middleware/logger.js';
const port = process.env.PORT || 8000;
const app = express();
// Body parser middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false}));
// Logger middleware
app.use(logger);
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/api/posts', posts);
app.listen(port, () => console.log(`Server is running on port ${port}`));
Custom Error Handler
- 使用 API 測試工具、GET 方法
http://localhost:8000/api/test - 修改 posts.js 檔案
- 在 middleware 資料夾裡面建立 error.js 檔案
- 修改 server.js 檔案
- 使用 API 測試工具、GET 方法
http://localhost:8000/api/posts/100 - 修改 posts.js 檔案
- 使用 API 測試工具、POST 方法、未填入 title
http://localhost:8000/api/posts
補上 next
// posts.js
import express from 'express';
const router = express.Router();
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// Get all posts
router.get('/', (req, res, next) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
});
// Get single post
router.get('/:id', (req, res, next) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
const error = new Error(`A post with the id of ${id} was not found`);
error.status = 404;
return next(error);
}
res.status(200).json(post);
});
// Create new post
router.post('/', (req, res, next) => {
const newPost = {
id: posts.length + 1,
title: req.body.title
};
if (!newPost.title) {
const error = new Error(`Please include a title`);
error.status = 400;
return next(error);
}
posts.push(newPost);
res.status(201).json(posts);
});
// Update Post
router.put('/:id', (req, res, next) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
const error = new Error(`A post with the id of ${id} was not found`);
error.status = 404;
return next(error);
}
post.title = req.body.title;
res.status(200).json(posts);
});
// Delete Post
router.delete('/:id', (req, res, next) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
const error = new Error(`A post with the id of ${id} was not found`);
error.status = 404;
return next(error);
}
posts = posts.filter((post) => post.id !== id);
res.status(200).json(posts);
});
export default router;
// error.js
const errorHandler = (err, req, res, next) => {
if (err.status) {
res.status(err.status).json({ msg: err.message });
} else {
res.status(500).json({ msg: err.message });
}
res.status(404).json({ msg: err.message });
};
export default errorHandler;
// server.js
import express from 'express';
import path from 'path';
import posts from './routes/posts.js';
import logger from './middleware/logger.js';
import errorHandler from './middleware/error.js';
const port = process.env.PORT || 8000;
const app = express();
// Body parser middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false}));
// Logger middleware
app.use(logger);
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/api/posts', posts);
// Error handler
app.use(errorHandler);
app.listen(port, () => console.log(`Server is running on port ${port}`));
Catch All Error Middleware
- 使用 API 測試工具、GET 方法
http://localhost:8000/api/test - 修改 server.js 檔案
- 再次使用 API 測試工具
- 在 middleware 資料夾裡面建立 notFound.js 檔案
- 修改 server.js 檔案
- 再次使用 API 測試工具
// server.js
import express from 'express';
import path from 'path';
import posts from './routes/posts.js';
import logger from './middleware/logger.js';
import errorHandler from './middleware/error.js';
import notFound from './middleware/notFound.js';
const port = process.env.PORT || 8000;
const app = express();
// Body parser middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false}));
// Logger middleware
app.use(logger);
// setup static folder
// app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/api/posts', posts);
// Error handler
app.use(notFound);
app.use(errorHandler);
app.listen(port, () => console.log(`Server is running on port ${port}`));
// notFound.js
const notFound = (req, res, next) => {
const error = new Error('Not Found');
error.status = 404;
next(error);
};
export default notFound;
產生錯誤
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
// error.js - 個人嘗試修正
const errorHandler = (err, req, res, next) => {
if (err.status) {
return res.status(err.status).json({ msg: err.message });
} else {
return res.status(500).json({ msg: err.message });
}
return res.status(404).json({ msg: err.message });
};
export default errorHandler;
Colors Package
- 安裝 Colors 套件
npm install colors - 修改 logger.js 檔案
- 使用 API 測試工具、GET 方法
http://localhost:8000/api/test - 再使用 API 測試工具、GET、POST、PUT、DELETE 方法
// logger.js
import colors from 'colors';
const logger = (req, res, next) => {
const methodColors = {
GET: 'green',
POST: 'blue',
PUT: 'yellow',
DELETE: 'red'
};
const color = methodColors[req.method] || white;
console.log(
`${req.method} ${req.protocol}://${req.get('host')}${req.originalUrl}`[
color
]
);
next();
};
export default logger;
Using Controllers
- 建立 controllers 資料夾
- 在 controllers 資料夾裡面建立 postController.js 檔案
- 修改 posts.js 檔案
- 修改 postController.js 檔案
- 使用 API 測試工具、GET 方法
http://localhost:8000/api/posts/2
http://localhost:8000/api/posts
http://localhost:8000/api/posts/43
// controllers/postController.js
let posts = [
{ id: 1, title: 'Post One' },
{ id: 2, title: 'Post Two' },
{ id: 3, title: 'Post Three' },
];
// @desc Get all posts
// @route GET /api/posts
export const getPosts = (req, res, next) => {
const limit = parseInt(req.query.limit);
if (!isNaN(limit) && limit > 0) {
return res.status(200).json(posts.slice(0, limit));
}
res.status(200).json(posts);
};
// @desc Get single post
// @route GET /api/posts/:id
export const getPost = (req, res, next) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
const error = new Error(`A post with the id of ${id} was not found`);
error.status = 404;
return next(error);
}
res.status(200).json(post);
};
// @desc Create new post
// @route POST /api/posts
export const createPost = (req, res, next) => {
const newPost = {
id: posts.length + 1,
title: req.body.title
};
if (!newPost.title) {
const error = new Error(`Please include a title`);
error.status = 400;
return next(error);
}
posts.push(newPost);
res.status(201).json(posts);
};
// @desc Update post
// @route PUT /api/posts/:id
export const updatePost = (req, res, next) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
const error = new Error(`A post with the id of ${id} was not found`);
error.status = 404;
return next(error);
}
post.title = req.body.title;
res.status(200).json(posts);
};
// @desc Delete post
// @route DELETE /api/posts/:id
export const deletePost = (req, res, next) => {
const id = parseInt(req.params.id);
const post = posts.find((post) => post.id === id);
if (!post) {
const error = new Error(`A post with the id of ${id} was not found`);
error.status = 404;
return next(error);
}
posts = posts.filter((post) => post.id !== id);
res.status(200).json(posts);
};
// post.js
import express from 'express';
import {
getPosts,
getPost,
createPost,
updatePost,
deletePost,
} from '../controllers/postController.js';
const router = express.Router();
// Get all posts
router.get('/', getPosts);
// Get single post
router.get('/:id', getPost);
// Create new post
router.post('/', createPost);
// Update Post
router.put('/:id', updatePost);
// Delete Post
router.delete('/:id', deletePost);
export default router;
__dirname Workaround
- 修改 server.js 檔案
- 在瀏覽器網址列輸入
http://localhost:8000/
http://localhost:8000/about.html
// server.js
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
import posts from './routes/posts.js';
import logger from './middleware/logger.js';
import errorHandler from './middleware/error.js';
import notFound from './middleware/notFound.js';
const port = process.env.PORT || 8000;
// Get the directory name
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
// Body parser middleware
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// Logger middleware
app.use(logger);
// setup static folder
app.use(express.static(path.join(__dirname, 'public')));
// Routes
app.use('/api/posts', posts);
// Error handler
app.use(notFound);
app.use(errorHandler);
app.listen(port, () => console.log(`Server is running on port ${port}`));
Making Requests From Frontend
- 修改 public/index.html 檔案
- 在 public 資料夾下建立 js/main.js 檔案
- 使用 API 測試工具、POST 方法
http://localhost:8000/api/posts
Body > x-www-form-urlencoded 加上 title、New Post
// public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Posts</title>
</head>
<body>
<h1>Posts</h1>
<button id="get-posts-btn">Get Posts</button>
<div id="output"></div>
<script src="./js/main.js"></script>
</body>
</html>
// public/js/main.js
const output = document.querySelector('#output');
const button = document.querySelector('#get-posts-btn');
// Get and show posts
async function showPosts() {
try {
const res = await fetch('http://localhost:8000/api/posts');
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
const posts = await res.json();
output.innerHTML = '';
posts.forEach((post) => {
const postEl = document.createElement('div');
postEl.textContent = post.title;
output.appendChild(postEl);
})
} catch (error) {
console.log('Error fetching posts: ', error);
}
}
// Event listeners
button.addEventListener('click', showPosts);
Submit Form to API
- 修改 index.html 檔案
- 修改 main.js 檔案
- 使用網頁畫面完成新增 Post 功能
http://localhost:8000
// public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Posts</title>
</head>
<body>
<h1>Posts</h1>
<form id="add-post-form">
<label for="title">Title:</label><br>
<input type="text" id="title" name="title"><br>
<button type="submit">Add Post</button>
</form>
<button id="get-posts-btn">Get Posts</button>
<div id="output"></div>
<script src="./js/main.js"></script>
</body>
</html>
// public/js/main.js
const output = document.querySelector('#output');
const button = document.querySelector('#get-posts-btn');
const form = document.querySelector('#add-post-form');
// Get and show posts
async function showPosts() {
try {
const res = await fetch('http://localhost:8000/api/posts');
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
const posts = await res.json();
output.innerHTML = '';
posts.forEach((post) => {
const postEl = document.createElement('div');
postEl.textContent = post.title;
output.appendChild(postEl);
})
} catch (error) {
console.log('Error fetching posts: ', error);
}
}
// Submit new post
async function addPost(e) {
e.preventDefault();
const formData = new FormData(this);
const title = formData.get('title');
try {
const res = await fetch('http://localhost:8000/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({title})
})
if (!res.ok) {
throw new Error('Failed to add post');
}
const newPost = await res.json();
const postEl = document.createElement('div');
postEl.textContent = newPost.title;
output.appendChild(postEl);
showPosts();
} catch (error) {
console.error('Error adding post');
}
}
// Event listeners
button.addEventListener('click', showPosts);
form.addEventListener('submit', addPost);
EJS Template Engine Setup
- 建立新的專案資料夾 express2
- 建立 package.json 檔案
使用終端機 npm init -y - 建立 app.js 檔案
- 安裝 express、ejs 套件
- 修改 package.json 檔案
- 建立 views 資料夾
- 在 views 資料夾裡面建立 index.ejs 檔案
- 修改 index.ejs 檔案
- 使用終端機執行指令 npm run dev
http://localhost:8000/
// package.json
{
"name": "express2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node app",
"dev": "node --watch app"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"ejs": "^3.1.10",
"express": "^4.19.2"
}
}
// app.js
import express from 'express';
const app = express();
// Config ejs
app.set('view engine', 'ejs');
app.set('views', 'views');
app.get('/', (req, res) => {
res.render('index');
});
app.listen(8000, () => console.log('Server started'));
// views/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Pass Data to Views
- 修改 app.js 檔案
- 修改 index.ejs 檔案
// app.js
import express from 'express';
const app = express();
// Config ejs
app.set('view engine', 'ejs');
app.set('views', 'views');
app.get('/', (req, res) => {
res.render('index', {
title: 'My Website',
message: 'Hello from EJS',
});
});
app.listen(8000, () => console.log('Server started'));
// index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<body>
<h1><%= message %></h1>
</body>
</html>
Pass and Loop Over Arrays
- 修改 app.js 檔案
- 修改 index.ejs 檔案
// app.js
import express from 'express';
const app = express();
// Config ejs
app.set('view engine', 'ejs');
app.set('views', 'views');
app.get('/', (req, res) => {
res.render('index', {
title: 'My Website',
message: 'Hello from EJS',
people: ['John', 'Jane', 'Jack'],
});
});
app.listen(8000, () => console.log('Server started'));
// index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<body>
<h1><%= message %></h1>
<ul>
<% people.forEach(person => { %>
<li><%= person %></li>
<% }); %>
</ul>
</body>
</html>
Template Partials
- 在 views 資料夾裡面建立 partials 資料夾
- 在 partials 資料夾裡面建立 header.ejs 檔案
- 修改 index.ejs 檔案
// header.ejs
<header>
<h1>My Website</h1>
</header>
// index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
</head>
<body>
<%- include('partials/header.ejs') %>
<h1><%= message %></h1>
<ul>
<% people.forEach(person => { %>
<li><%= person %></li>
<% }); %>
</ul>
</body>
</html>