尚硅谷 Node.js 教程
The fs
module
Write content to a file asynchronously
1
2
3
4
5
6
7
8
9const fs = require("fs");
fs.writeFile("./file1.txt", "writeContent", (err) => {
if (err) {
console.log(err);
return;
}
console.log("Write Succeeded");
});Write content to a file synchronously
1
2
3const fs = require("fs");
fs.writeFileSync("./file1.txt", "writeContent");Append content to a file
1
2
3
4
5
6
7
8
9
10
11
12
13const fs = require("fs");
// This is equivalent to fs.writeFile('./file1.txt', '\nappendContent', 'a', (res) => {...});
fs.appendFile("./file1.txt", "\nappendContent", (err) => {
if (err) {
console.log("Error occured");
return;
}
console.log("Append succeeded");
});
// This is equivalent to fs.writeFileSync('./file1.txt', '\nappendContent', {options: 'a'});
fs.appendFileSync("./file.txt", "\nappendContent");Write content stream to a file
1
2
3
4
5
6const fs = require("fs");
const ws = fs.createWriteStream("./file1.txt");
ws.write("writeContent1\n");
ws.write("writeContent2\n");
ws.close();Read content from a file asynchronously
1
2
3
4
5
6
7
8
9
10const fs = require("fs");
fs.readFile("./file1.txt", (err, data) => {
if (err) {
console.log("Error occured");
return;
}
// data is a buffer, need toString method to print its content
console.log(data.toString());
});Read content from a file synchronously
1
2
3
4const fs = require("fs");
const data = fs.readFileSync("./file1.txt");
console.log(data.toString());Read content stream from a file
1
2
3
4
5
6
7
8
9
10
11
12const fs = require("fs");
const rs = fs.createReadStream("./file1.txt");
rs.on("data", (chunk) => {
// default chunk size is 65kb
console.log(chunk);
});
rs.on("end", () => {
console.log("Read finished");
});Reanme a file
1
2
3
4
5
6
7
8
9const fs = require("fs");
fs.rename("./fromPath.txt", "./toPath.txt", (err) => {
if (err) {
console.log(err);
return;
}
console.log("Rename finished");
});Delete a file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const fs = require("fs");
fs.unlink("./file1.txt", (err) => {
if (err) {
console.log(err);
return;
}
console.log("File deleted");
});
fs.unlinkSync("./file11.txt");
fs.rm("./file2.txt", (err) => {
if (err) {
console.log(err);
return;
}
console.log("File deleted");
});
fs.rmSync("./file22.txt");Directory operations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38const fs = require("fs");
// Create a directory
fs.mkdir("./dir1", (err) => {
if (err) {
console.log(err);
return;
}
console.log("Directory created");
});
// Recursively create directory
fs.mkdir("./a/b/c", { recursive: true }, (err) => {
if (err) {
console.log(err);
return;
}
console.log("Directory created");
});
// Notice fs.rmdir is deprecated, use fs.rm instead
// Delete an empty directory
fs.rmdir("./dir/b/c", (err) => {
if (err) {
console.log(err);
return;
}
console.log("Directory created");
});
// Recursively delete a directory
fs.rmdir("./dir/b/c", { recursive: true }, (err) => {
if (err) {
console.log(err);
return;
}
console.log("Directory created");
});Check resource status
1
2
3
4
5
6
7
8
9
10
11
12
13const fs = require("fs");
fs.stat("./fil1.txt", (err, data) => {
if (err) {
console.log(err);
return;
}
console.log(data);
// Is target a file
console.log(data.isFile());
// Is target a directory
console.log(data.isDirectory());
});The relative path that
fs
module uses is not relative to the path of the node.js file, it is actually relative to the command line path (the path where you enter thenode xxx.js
command). Inorder to fix this bug, you can use absolute path. A trick to get the absolute path is to use the__dirname
variable to get the absolute path of current folder, so__dirname
+/xxx.js
is the absolute path ofxxx.js
file
The path
module
Get formal absolute path with correct delimiter
1
2
3
4
5
6
7const fs = require("fs");
const path = require("path");
// This one works as a valid path, but it is not reabably correct
const wrongAbsPath = __dirname + "/index.html";
// This absolute path has the correct ddelimiters
const corrAbsPath = path.resolve(__dirname, `./index.html`);Get system separator:
path.sep
Get the absolute path of the current directory:
__dirname
Get the absolute path of the current file:
__filename
Get a JSON of the absolute path:
path.parse(pathURL)
Get the basic file name:
path.basename(pathURL)
Get the directory name:
path.basename(pathURL)
Get the file extension name:
path.basename(pathURL)
HTTP
- HTTP request
- Format: request line + request header lines + request body
- Request line: method + URL + HTTP version
- Request header: key-value pairs for metadata
- Request body: can contain any data
- HTTP response
- Format: response line + response header lines + response body
- Response line: HTTP version + status code + reason phrase
- Response header: key-value pairs for metadata
- Response body: can contain different types of data
The http
nodule
Create a simple http server
1
2
3
4
5
6
7
8
9
10
11
12
13
14const http = require("http");
// The callback is invoked the the server receives a request
const server = http.createServer((req, res) => {
// Display content in utf-8, so some contents like Chinese can be
// displayed correctly
res.setHeader("content-type", "text/html;charset=utf-8");
res.end("Server response here我们");
});
// The callback is invoked when the server starts
server.listen(9000, () => {
console.log("Server Started");
});Default port for HTTP is 80, default port for HTTPS is 443
Get request method
req.method
Get URL:
req.url
, forlocalhost:9000/test
, this returns/test
Get request HTTP version:
req.httpVersion
Get request header:
req.headers
, return an object with all keys in lowercaseGet request body
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const http = require("http");
const server = http.createServer((req, res) => {
let body = "";
req.on("data", (chunk) => {
// body += chunk also works, the chunk buffer will be automatically
// converted to string
body += chunk.toString();
});
req.on("end", () => {
console.log(body);
res.end("Server response here");
});
});
server.listen(9000, () => {
console.log("Server started");
});Get request URL and query string with the
url
module1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const http = require("http");
const url = require("url");
const server = http.createServer((req, res) => {
// The second argument converts the query from string to object
const res = url.parse(req.url, true);
const pathname = res.pathname;
console.log(pathname);
const value = res.query.key;
console.log(value);
res.end("Server response here");
});
server.listen(9000, () => {
console.log("Server started");
});Another way to get query value without use of
url
module1
2
3
4
5
6
7
8
9
10
11const http = require("http");
const server = http.createServer((req, res) => {
const url = new URL(req.url, "http://localhost");
let value = url.searchParams.get(key);
console.log(value);
});
server.listen(9000, () => {
console.log("Server started");
});Set response status code:
res.statusCode = xxx
Set response status message:
res.statusMessage = xxx
Set response header:
res.setHeader(key, value)
, multiple values with one key:res.setHeader(key, [values])
Set response body:
res.write(content); res.end();
Notice
let docs = document.getElementsByTagName(name)
returns aHTMLCollection
and cannot useforEach
directly, you need to useArray.from(docs).forEach()
let docs = document.querySelectorAll(name)
returns aNodeList
and can useforEach
directlyRespond to different file types
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const http = require("http");
const fs = require("fs");
const server = http.createServer((req, res) => {
const url = req.url;
if (url === "/") {
res.setHeader("content-type", "text/html;charset-utf-8;");
const html = fs.readFileSync(__dirname + "/response.html");
res.end(html);
} else if (url.includes(".css")) {
const css = fs.readFileSync(__dirname + "/style.css");
res.end(css);
} else if (url.includes(".js")) {
const script = fs.readFileSync(__dirname + "/script.js");
res.end(script);
} else {
res.statusCode = 404;
res.end("<h1> 404 Not Found.</h1>");
}
});
server.listen(9000, () => {
console.log("Server started");
});Absolute Path and Relative path
- Absolute path
https://www.google.com
directly send request to the url, used with external links//test.com/web
combine URL with current portocol and then send request, examplehttp://test.com/web
/test.com/web
combine URL with current portocol, server name, port number and then send request, examplehttp://localhost:9000/test.com/web
- Relative path: do calculation first, then send request. You should use absolute path instead of relative path when possible
Set MIME(Multipurpose Internet Mail Extensions) type
- Use file extension to get MIME type
1
2
3
4const pathname = url.parse(req.url, true);
const filePath = __dirname + pathname;
// remote . in the extension name
const ext = path.extname(filePath).slice(1);- Use a map from extension name to MIME types
1
2
3
4
5
6
7
8let type = mappng[ext];
if (type) {
// if content type is valid
res.setHeader("content-type", type);
} else {
// used for unknown binary files
res.setHeader("content-type", "application/octet-stream");
}Charset problem
res.setHeader('content-type', 'test/xxx;charset=utf-8');
- In html, you can set
<meta charset="UTF-8">
. But response heder has high priority if the mata and response header have different charsets - You do not need to charsets for stylesheets and scripts, they will automatically be converted to the same charset as the HTML file by browser
- Handling process
1
2
3
4
5
6
7
8
9
10
11let type = mapping[ext];
if (ext === "html") {
type += ";charset=utf-8";
}
if (type) {
// if content type is valid
res.setHeader("content-type", type);
} else {
// used for unknown binary files
res.setHeader("content-type", "application/octet-stream");
}Error Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// Method not allowed
if (req.method !== "GET") {
res.statusCode = 405;
res.end("<h1>405 Method Not allowed</h1>");
// Should have return here
return;
}
if (err) {
switch (err.code) {
// No file or directory
case "ENOENT":
res.statusCode = 404;
res.end("<h1>Resource Not Found</h1>");
// Operation not allowed
case "ETERM":
res.statusCode = 403;
res.end("<h1>403 Forbidden</h1>");
default:
res.statusCode = 500;
res.end("<h1>Server internal error</h1>");
}
}
Modularity
- Export data
1
2module.exports = { a, b };
exports.a = a;requirex(xxx)
retrieves themodule.exports
object in a module- Initially,
exports = module.exports = {}
- Use relative path for user-made module, extension for js file and json file can be omitted
require(directory)
- Checks the
package.json
, finds themain
property, and import the corresponding value file - If
main
property orpackage.json
does not exist, tries to importindex.js
andindex.json
- Raises an error
- Checks the
- Node.js is compatible with CommonJS(
require
style) and ECMAScript(import
style)
- npm
npm -v
,npm init -y
npm s <name>
(search package), you can also search packages onhttps://www.npmjs.com/
,npm i <name>@<version>
(install package for a specific version),npm r <name>
(remote package)pack-lock.json
(used to hold information about exact package version number)- For NPM packages, simply write its name in the
require
function, this will allow npm to search hierarchically for the package upward until the root directory of the disk - Some packages are development
dependencies(
npm i -S <name>
), some packages are production dependencies(npm i -D <name>
) - Global installation:
npm i -g <name>
, global uninstallation:npm r -g <name>
] - Run scripts:
npm run <script-name>
, for scripts with keystart
, you can simply usenpm start
- yarn
- yarn is a fast, secure and reliable node package manager,
npm i -g yarn
yarn init -y
,yarn add <name>
,yarn
(install add required dependencies),yarn <script-name>
,yarn global install <name>
(need to add the global package folder to the path environment variable)
yarn.lock
is the lockfile for yarn-managed project- Only use npm or yark for a project, do not mix them together
- Package managers
composer
for PHP,pip
for Python,maven
for Java,go mod
for go,npm/yarn
for JavaScript,rubyGems
for Rubyyum
for Centos,apt
for Ubuntu,homebrew
for MacOS,chocolatey
for Windows
- yarn is a fast, secure and reliable node package manager,
- nvm
- Node Version Manager, used to switch Node.js version
nvm install x.x.x
,nvm use x.x.x
,nvm uninstall x.x.x
,nvm list
Express.js
An example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const express = require("express");
const port = 9000;
const app = express();
app.get("/", (req, res) => {
res.end("<h1> Hello Express.</h1>");
});
app.get("/:id", (req, res) => {
const id = req.params["id"];
res.end(`<h1>Hello Page ${id}</h1>`);
});
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});Router:
router.method(url, callback)
,all
,get
,post
, ...Request
Get some parameters:
- Native parameters:
req.method
,req.url
,req.httpVersion
,req.headers
- Express parameters:
req.path
,req.query
,req.ip
- Native parameters:
Get URL parameters:
req.params[key]
orreq.params.key
Get Request Body using the
body-parser
package(npm i body-parser
)1
2
3
4
5
6
7
8
9
10
11
12const bodyParser = require("body-parser");
// Define body parse for application/x-www-form-urlencoded data
const urlencodedParser = bodyParser.urlencoded({ extended: false });
// Use router-level body parser
// After body-parser is executed, there will be a body attribute in the req object,
// then you can access data using req.body.key
app.post("/login", urlencodedParser, (req, res) => {
console.log(req.body);
res.end();
});
Response
- Native parameters:
res.statusCode
,res.statusMessage
,res.write()
,res.end()
- Express method:
res.send()
, this method will automatically setcontent-type
in response headers res.redirect(url)
,res.download(resourceAbsolutePath)
,res.json(jsonObject)
,res.sendFile(resourceAbsolutePath)
you need to setcontent-type
in header for some file types, otherwise files like JSON will be downloaded instead of being displayed in the brwoser
- Native parameters:
Middleware
Application-level middleware: handle all requests
1
2
3
4
5
6
7
8
9
10
11const recordMiddleware = (req, res, next) => {
let { url, ip } = req;
fs.appendFileSync(
path.resolve(__dirname, "./accesss.log"),
`Requested ${url} from ${ip} \n`
);
// You need to call next function to continue to execute
next();
};
app.use(recordMiddleware);Router-level middleware: handle requests to URLs specified by a router. Can be used for user authentication
1
2
3
4
5
6
7
8
9
10
11
12
13const checkSecret = (req, res, next) => {
if (url.query.secret === "secret") {
// If secret is corret, continue to execute
next();
} else {
res.send("<h1>Wrong secret </h1>");
}
};
// When requests is made to /admin, the middleware function is executed first
app.get("/admin", checkSecret, (req, res) => {
res.send("<h1>Admin Page</h1>");
});Built-in middleware for static resources
1
app.use(express.static(__dirname + "/public"));
- This middleware will search for
index.html
that is directly located inside the public folder, and will use the html to respond requests to/
- If you set router to handle requests to
/
, then it depends the order that whether your router or the static resource middleware is executed first - This static resource middleware has nothing to do with the absolute
path in the
res.sendFile(path)
function, you still need to specify the absolute paht1
2
3
4app.get("/login", (req, res) => {
let filePath = path.join(__dirname, "./public/login.html");
res.sendFile(filePath);
});
- This middleware will search for
Anti-leech protection(防盗链)
Resources can only be accessed from the hosting website, and cannot be accessed from other websites with a link to the original hosting website
Idea: check the
referer
attribute in the request header, only send response when referer is the same as the hosing website1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const checkReferer = (req, res, next) => {
// get attribute from request header
let referer = req.get("referer");
if (referer) {
let url = new URL(referer);
if (url.hostname != "127.0.0.1") {
res.statusCode = 404;
res.send("<h1>404 Not Found</h1>");
return;
}
}
next();
};
app.use(checkReferer);
Router modularity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// in homerouter.js
const express = require("express");
const path = require("path");
const router = express.Router();
router.get("/home", (req, res) => {
let loginPath = path.join(__dirname, "./public/index.html");
res.sendFile(loginPath);
});
router.get("/login", (req, res) => {
let loginPath = path.join(__dirname, "./public/login.html");
res.sendFile(loginPath);
});
module.exports = router;1
2
3
4// in app.js
const homeRouter = require("./06homerouter");
app.use("/", homeRouter);EJS template
An example
1
2
3
4
5
6
7
8
9
10const ejs = require("ejs");
const fs = require("fs");
const country = "United States";
const title = "EJS example";
let template = fs.readFileSync("./07learn-ejs.ejs").toString();
let result = ejs.render(template, { title: title, country: country });
console.log(result);Control flow: notice that you can use
-%>
instead of%>
to trim empty lines in the control flow statements1
2
3
4
5
6
7
8
9
10const ejs = require("ejs");
const fs = require("fs");
const arr = ["name1", "name2", "name3", "name4"];
const title = "EJS Loop";
let template = fs.readFileSync("./08ejs-loop.html").toString();
let result = ejs.render(template, { arr: arr, title: title });
console.log(result);Use EJS in express
- Set template engine:
app.set("view engine", "ejs");
- Set templates location directory:
app.set("views", path.resolve(__dirname, "./views"));
, the template files should have extension.ejs
- Render EJS templates
res.render("templatename", dataObject)
- File hierarchy: for
view/auth/auth.js
, the render method should beres.render(auth/auth.js)
- Set template engine:
Express application generator
- Installation:
npm i -g express-generator
- Generate project:
express --view=ejs project
, orexpress -e project
- The template project uses two middlewares to let you access
body:
app.use(express.json());
forapplication/json
, andapp.use(express.urlencoded({ extended: false }));
forapplication/x-www-form-urlencoded
, so you can usereq.body
directly - Express port settings
- Use
process.env.PORT
or a default port in express1
2
3// the normalizePort method checks if process.env.PORT is a valid number
const port = normalizePort(process.env.PORT || '8080');
app.set('port', port); - Set environment variables: in Command Prompt:
set PORT=5000
, in Power Shell:$env:PORT=5000
, in Bash/Macexport PORT=5000
- Use
- Installation:
File Upload
- Form attribute for file upload
1
2
3
4
5<!-- The enctype is required for file upload -->
<form action="/url" method="post" enctype="multipart/form-data">
<label for="portrait">Image</label>
<input type="file" id="portrait" name="portrait" /><br />
</form> - Notice the built-in express middleware cannot handle file upload
with
enctype=multipart/form-data
, you need external packages to handle multipart likemulter
orformidable
- Logic: when a file is uploaded to the server, the file itself is saved in the static resource directory on the server, its corresponding new url is saved into database for future reference
- An example using formidable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const formidable = require("formidable");
// Other codes are omitted
router.post("/register", (req, res) => {
// Create form object
const form = formidable({
multiples: true,
// Let the file be uploaded into the following static resource directory
uploadDir: __dirname + "/../public/files",
// Keep file extension
keepExtensions: true,
});
form.parse(req, (err, fields, files) => {
if (err) {
next(err);
return;
}
// Get the new file url for future request, this url points to the file location in the static resource directory
let url = "/files/" + files.portrait.newFilename;
res.json({ fields, url: url });
});
});
- Form attribute for file upload
The lowdb module
- A simple to use local JSON databse. Use native JavaScript API to query. Written in TypeScript
- Notice lowdb is now a pure ESM package(the ECMAScript style), in
order to use CommonJS style, use lowdb 1.0.0
npm i lowdb@1.0.0
- Some methods like
write()
,push()
,assign()
,remove()
, ...
MongoDB
Introduction
- database -> collection -> document
show dbs
,use <db>
,db
,db.createCollection(<collection>)
,db.dropDatabase()
show collections
,db.<collection>.drop
,db.<collection>.renameCollection(<collection>)
db.<collection>.insert(<document>)
,db.<collection>.find(<condition>)
,db.<collection>.updateOne/updateMany(<condition>, {$set: <new-value>})
,db.<collection>.deleteOne/deleteMany(<condition>)
- You can use mongo shell to connect to MongoDB: run
mongosh
in cmd, and you will connect to the local MongoDb database running atlocalhost:27017
The
mongoose
module- Installation:
npm i mongoose --save
- In mongoose 7.x.x version, support for callback is removed
(Reference:
7.0.0-rc0 / 2023-02-23 update), so you need
npm i mongoose@6.10.0
in order to use callbacks - An example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const mongoose = require("mongoose");
const db = "cosi152";
mongoose.connect(`mongodb://127.0.0.1:27017/${db}`);
mongoose.connection.once("open", () => {
console.log("Connected to database");
});
mongoose.connection.on("error", () => {
console.log("Error occured");
});
mongoose.connection.on("close", () => {
console.log("Connection terminated");
});
setTimeout(() => {
mongoose.disconnect();
}, 2000); - Insert document:
<Model>.create()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15mongoose.connection.on("open", () => {
console.log("Connected to database");
let accountSchema = new mongoose.Schema({
id: Number,
name: String,
age: Number,
});
let bookModel = mongoose.model("accounts", accountSchema);
bookModel.create({ id: 3, name: "Wendy", age: 22 }, (err, data) => {
if (err) {
console.log(err);
}
console.log(data);
});
}); - Schema datatype:
String
,Number
,Boolean
,Array
,Date
,Buffer(can store binary files, but static resources are commonly stored on the server static resource directory instead)
,Mixed(mongoose.Schema.Types.Mixed)
,ObjectId(commonly used for foreign key)
,Decimal128
- Mongoose field validation:
required: true
,default: <value>
,enum: [...<values>]
,unique: true
(in order for unique to have effect, you need to drop and then recreate the collection) - Delete document:
<Model>.deleteOne(<condition>, <callback>)
,<Model>.deleteMany()
, ...1
2
3
4
5
6
7
8
9
10
11mongoose.connection.on("open", () => {
console.log(`Databse connected to ${url}`);
const arr = await BookModel.find();
BookModel.deleteOne({ _id: "6418b5cf5cbd30962f396157" }, (err, data) => {
if (err) {
console.log(err);
return;
}
console.log(data);
});
}); - Update document:
<Model>.updateOne(<condition>, <newValue>, <callback>)
,<Model>.updateMany()
, ...1
2
3
4
5
6
7
8
9mongoose.connection.on("open", () => {
console.log(`Connected to ${url}`);
BookModel.updateOne({_id: "6418b5cf5cbd30962f396158"}, {name:"HongLouMeng"}, (err, data) => {
if (err) {
console.log(err);
}
console.log(data);
});
}); - Query
document:
<Model>.find(<condition>, <callback>)
,<Model>.findById(<id>, <callback>)
,<Model>.findOne(<condition>, <callback>)
, ...1
2
3
4
5
6
7
8
9
10mongoose.connection.on("open", () => {
console.log(`Connected to ${url}`);
BookModel.findOne({_id: "6418b5cf5cbd30962f396158"}, (err, data) => {
if (err) {
console.log(err);
}
console.log(data);
console.log(data.length);
});
}); - Conditions:
- Operators:
$lt
,$gt
,$lte
,$gte
,$ne
,{price: {$lt: 10}}
- Logic operators:
$or
,$and
,{ $and: [{ price: { $lt: 20 } }, { price: { $gt: 10 } }] }
- Regular expression:
{name: /123/}
,{name: new RegExp("123")}
(can be used to match values passed from variables)
- Operators:
- Customized query
- Only return specific fields from queries:
BookModel.find().select(field: 1).exec(callback)
, only fields with value 1 in the select method will be returned - Sort results:
BookModel.find().sort(price: 1).exec(callback)
,1 means ascending, -1 means descending - Limit results with offsets:
BookModel.find().sort(price: 0).skip(m).limit(n).exec(callback)
, select n items with top m items skipped
- Only return specific fields from queries:
- Modularized mongoose connection
1
2
3
4
5
6
7// config.js
module.exports = {
dbhost: "127.0.0.1",
dbport: 27017,
dbname: "cosi152"
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// dbConnect.js
const mongoose = require("mongoose");
const {dbhost, dbport, dbname} = require("./config");
const dbConnect = (success, fail) => {
const db = "cosi152";
const url = `mongodb://${dbhost}:${dbport}/${db}`;
mongoose.connect(url);
mongoose.connection.on("open", () => {
console.log(`Connected to ${url}`);
success();
});
mongoose.connection.on("error", () => {
console.log("Error occured");
fail();
});
mongoose.connection.on("close", () => {
console.log(`Disconnected from ${url}`);
});
setTimeout(() => {
mongoose.disconnect();
}, 2000);
};
module.exports = dbConnect;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// usage.js
const mongoose = require("mongoose");
const BookModel = require("./book");
const dbConnect = require("./db");
const success = () => {
BookModel.find({ price: { $gt: 10 } })
.select({ author: 1, price: 1 })
.sort({ price: 1 })
.limit(3)
.exec((err, data) => {
console.log(data);
});
};
const fail = () => {};
dbConnect(success, fail);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// book.js
const mongoose = require("mongoose");
const BookSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
is_hot: {
type: Boolean,
required: true,
},
});
const BookModel = mongoose.model("novels", BookSchema);
module.exports = BookModel; - Promise chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26new movieModel({ title: "The Wandering Earth 2", director: "Fan Guo" })
.save() // save in the database, return saved document
.then((movie) => {
console.log(`Saved movie: ${movie}`); // print saved document
return movieModel.findOne({ title: movie.title }); // find a document, return found document
})
.then((movie) => {
console.log(`Found movie:, ${movie}`); // print found document
return movieModel.findOneAndUpdate( // find a document and update it
{ title: movie.title },
{ director: "123" },
{ returnDocument: "after" } // return updated document rather than the original one
);
})
.then((movie) => {
console.log(`Updated movie: ,${movie}`); // print updated document
return movieModel.findOneAndDelete({ title: movie.title }); // find a document and delete it, return deleted document
})
.then((movie) => {
console.log(`Delete movie: ${movie}`); // print deleted document
return movieModel.find(); // find all documents in the database
})
.then((results) => {
console.log(`Data in the database: ${results}`); // print all documents in the database
})
.catch((error) => console.log("Error:", error));
- Installation:
Graphical MongoDB Tools
- Robo 3T
- Navicat for MongoDB
- MongoDB Atlas
Convert string to Date: use the
moment
packagemoment("2023-03-21")
convert string to objectmoment("2023-03-21").toDate()
convert object to Date objectmoment(new Date()).format("YYYY-MM-DD")
convert date to YYYY-MM-DD format
Use alert to double confirm deletion
1
2
3
4
5
6
7
8
9
10
11let btns = document.querySelectorAll(".myDelBtn");
btns.forEach((item) => {
item.addEventListener("click", (e) => {
// If user clicks yes, then return true, if the user clicks cancel, return false
if (confirm("Do you really want to delete the item?")) {
return true;
} else {
e.preventDefault();
}
});
});API
- The interface connecting frot-end and back-end
- The data interface usually transfers data using JSON
- API = method + URL + request parameters + reponse
RESTful API
Characteristics
- URL represent resource
- Type of resource operation shoule be the same as request type
- Response should contain proper status code
An example
Operation Request Method URL Returned Information Add a song POST /song Return info of added song Delete a song DELETE /song/id Return empty document Modify a song PUT /song/id Return info of updated song Modify a song PATCH /song/id Return info of updated song Get all songs GET /song Return info array of all songs GET a song GET /song/id Return info of a specific song The
json-server
package- Get a full fake REST API with zero coding for testing
- Usage
- Install package
npm i -g json-server
- Create a json file called
db.json
, write your data structure - Run
json-server --watch db.json
in command line in the same directory as thedb.json
file - The server will start at
localhost:3000
- Install package
API testing tools: apipost, postman
Add API for project
- Add an api router
- Let the router return json format data
- Common JSON format
1
2
3
4
5{
code: res.statusCode,
msg: res.statusMessage,
data: data
}
Session Control
- Common session control techniques: cookie, session, token
- Cookie
- data saved in browser, same hostname has same cookie, hostname
cookie is wrapped in request header inside
cookie
attribute - Cookie is sent from server inside response header
set-cookie
attribute, and the browser saved cookie locally - Use cookie in express:
res.cookie(key, value)
, for multiple cookies, simply call this method multiple times. Cookies set using the above method expires when the browser is closed.res.cookie(key, value, {maxAge: <milliseconds>})
, cookie set in this way only expires when reaches its max age. when you use browser inspector, the maxAge is displayed in secondsres.clearCookie(key)
removes cookie specified by the key- User
cookie-parser
to get cookie from request- Install
npm i cookie-parser
- An example
1
2
3
4
5
6
7
8
9
10
11
12
13
14const cookieParser = require("cookie-parser");
app.user(cookieParser);
app.get('/', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies);
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies);
res.json(req.cookies);
})
app.listen(8080)
- Install
- data saved in browser, same hostname has same cookie, hostname
cookie is wrapped in request header inside
- Session
- Data saved on the server, can be used for user authentication: when user tries to login, server compare credentials in the database. If the info matches, server will create a session object with an unique session id and save the session object on the server. Then the server will set session id as cookie inside response header, so for later on user logins, the server knows user identity
- Use session in express and save session to MongoDB
- Installation:
npm i express-session connect-mongo
, express-session stores session in memory by default, we can use connect-mongo to store the session object into MongoDB - App session configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22app.use(
session({
// the name of the cookie that server sets in its response
name: "sid",
// the secret used to sign the session id cookie(generate hash session id), can be string or array of strings
secret: "yanxuanshaozhu",
// whether each request should be made a session
saveUninitialized: false,
// force the session be saved back to the session store, so user credentials are valid
resave: true,
// save session to MongoDB, the collection name is sessions
store: MongoStore.create({
mongoUrl: "mongodb://127.0.0.1:27017/cosi152",
}),
// set options for the cookie in the response
cookie: {
// if this is true, then client cannot access the session cookie in the response
httpOnly: true,
maxAge:1000 * 60
},
})
); - Let the server set session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23router.post("/login", (req, res) => {
let { username, password } = req.body;
userModel.findOne(
{ username: username, password: md5(password) },
(err, data) => {
if (err) {
res.status(500).send("Registration failed");
return;
}
if (!data) {
return res.send("Login failed");
}
// User successfully logined, set session cookie in response
req.session.username = data.username;
req.session._id = data._id;
res.render("success", {
msg: "Login success",
url: "/account",
title: "Success Page",
});
}
);
}); - Use session to check login status middleware:
1
2
3
4
5
6
7module.exports = (req, res, next) => {
if (!req.session.username) {
res.redirect("/loign");
}
// Do not forget to call next method to continue to execute other logics
next();
};
- Installation:
- Password encryption
- Password should be encrypted before being stored inside the database
- Use
md5
moduel to do password hashing:npm i md5
,password = md5(req.body.password)
- Protect CSRF
- Cross-Site Request Forgery (CSRF) is an attack that forces authenticated users to submit a request to a Web application against which they are currently authenticated
- Example: in website B, it sends a get request to website A with a cookie from website A, then user logging status can be changed on website A
- How to deal with this: Use
POST
request instead ofGET
request
- token
- Definition: a encrypted string sent from user to client to identify client identity
- tokent is commonly used in mobile apps for session controls
- cookie is automatically included in request header, but a client needs to manually include tokens inside the header of its requests
- Advantage of token
- Saved on client, so less pressure on the server
- Can prevent CSRF
- Tokens can be shared among services
- JWT(JSON Web Token)
- Enable secure claims between two parties as a JSON object
- Use JWT in javascript:
npm i jsonwebtoken
- Logic
- When client side credentials match records stored in the database, server will creat a token and send it to client
- For later on requests, the token is included in the request and sent to the server
尚硅谷其他教程
AJAX
- AJAX Basics
- Originally AJAX uses XML, now JSON is used more frequently
- AJAX GET Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<script type="text/javascript">
let btn = document.getElementById("send");
let result = document.getElementById("result");
btn.addEventListener("click", () => {
// Create XHR object
const xhr = new XMLHttpRequest();
// Specify request method and request URL
// If you send get request with query string, modify the request URL
xhr.open("GET", "http://127.0.0.1:9000/server?id=1&name=2");
// Send HTTP request
xhr.send();
// xhr.readystate an integer value representing http request status
xhr.onreadystatechange = () => {
// all data is returned from the server
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(xhr.status); // response status
console.log(xhr.statusText); // response status mesage
console.log(xhr.getAllResponseHeaders); // response header array
console.log(xhr.response); // response body
result.innerHTML = xhr.response;
}
}
};
});
</script> - AJAX POST Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<script type="text/javascript">
let btn = document.getElementById("send");
let result = document.getElementById("result");
btn.addEventListener("click", () => {
const xhr = new XMLHttpRequest();
xhr.open("POST", "http://127.0.0.1:9000/server");
xhr.setRequestHeader("Content-Type", "application/json");
const data =JSON.stringify({ id: "id1", name: "name1" });
xhr.send(data);
// xhr.readystate an integer value representing http request status
xhr.onreadystatechange = () => {
// all data is returned from the server
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
result.innerHTML = xhr.responseText;
}
}
};
});
</script> - Use XHR to set request type:
xhr.setRequestHeader("key", "value")
- If response data is JSON string, how to deal with it
- Manunally modify response:
const data = JSON.parse(xhr.response); console.log(data.id);
- Set response type, then response will be automatically converted to
JSON object:
res.responseType = "json"; console.log(xhr.response.id);
- Manunally modify response:
- Timeout handling and error handling
1
2
3
4
5
6
7
8xhr.timeout = 2000; // 2000 milliseconds
xhr.ontimeout = () => {
alert("Request cancelled due to timeout");
}
xhr.onerror = () => {
alert("Request cancelled due to error");
} - Cancel request before receiving response:
xhr.abort()
- Prevent resending the same request:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let isSending = false;
let xhr = null;
// Cancel current request
if (isSending) {
xhr.abort();
}
xhr = new XMLHttpRequest();
// Start sending request
isSending = true;
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
// Response has been received
isSending = false;
}
}
- Use AJAX with jQuery
- jQuery uses XMLHttpRequest in its implementation
- Send GET Request Example:
$.get(url, dataObject, successCallback(data), dataType)
1
2
3
4
5
6
7
8
9
10
11
12
13
14<script type="text/javascript">
$("button")
.eq(0)
.click(() => {
$.get(
"http://127.0.0.1:9000/serverjson",
{ id: "01", name: "name1" },
(data) => {
$(".container div").html(`id: ${data.id}, name: ${data.name}`);
},
"json"
);
});
</script> - Send POST request example:
$.post(url, dataObject, successCallback(data), dataType)
1
2
3
4
5
6
7
8
9
10
11
12
13
14<script type="text/javascript">
$("button")
.eq(1)
.click(function () {
$.post(
"http://127.0.0.1:9000/serverjson",
{ id: "01", name: "name1" },
(data) => {
$(".container div").html(`id: ${data.id}, name: ${data.name}`);
},
"json"
);
});
</script> - Send request using
$.ajax({})
example1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30<script type="text/javascript">
$("button")
.eq(2)
.click(() => {
$.ajax({
// url
url: "http://127.0.0.1:9000/serverjson",
// data
data: { id: "01", name: "name1" },
// request type
type: "GET",
// response dataType
dataType: "json",
// success callback
success: (data) => {
$(".container div").html(`id: ${data.id}, name: ${data.name}`);
},
//timeout time
timeout: 2000,
// error callback
error: () => {
alert("Error occured");
},
// request headers
headers: {
"Content-Type": "application/json",
},
});
});
</script>
- Use AJAX with axios
- axios uses XMLHttpRequest in its implementation
- Use axios to send GET request:
axios.get(url, {configurations})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<script type="text/javascript">
axios.defaults.baseURL = "http://127.0.0.1:9000";
document.getElementById("btn1").onclick = () => {
axios
.get("/serverjson", {
//url parameters
params: {
id: 1,
name: "name1",
},
// request header
headers: {
id: 2,
name: "name2",
},
responseType: "json",
})
.then((res) => {
document.getElementById(
"result"
).innerHTML = `id: ${res.data.id}, name: ${res.data.name}`;
})
.catch((error) => {
console.log(error);
});
};
</script> - Use axios to send POST request:
axios.post(url, dataObject, {configurations})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<script>
axios.defaults.baseURL = "http://127.0.0.1:9000";
document.getElementById("btn2").onclick = () => {
axios.post(
"/serverjson",
// data
{
id: 1,
name: "name1",
},
{
// url parameters
params: {
id: 2,
name: "name2",
},
// request headers
headers: {
id: 1,
name: "name1",
},
}
);
};
</script> - Use
axios
to send request:axios({})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<script>
axios.defaults.baseURL = "http://127.0.0.1:9000";
document.getElementById("btn3").onclick = () => {
axios({
// request method
method: "GET",
// request url
url: "/serverjson",
// url parameters
params: {
id: 1,
name: "name1",
},
// request headers
headers: {
id: 2,
name: "name2",
},
// request body data
data: {
id: 3,
name: "name3",
},
})
.then((response) => {
document.getElementById(
"result"
).innerHTML = `id: ${response.data.id}, name: ${response.data.name}`;
})
.catch((error) => {
console.log(error);
});
};
</script>
- Use
fetch
api- Send get request
1
2
3
4
5
6
7
8
9
10
11
12
13
14<script>
let btn = document.getElementById("send");
let result = document.getElementById("result");
btn.onclick = () => {
fetch("http://149.28.62.112:3000/server?id=1&name=name", {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
data = JSON.parse(data);
result.innerHTML = `id: ${data.id}, name: ${data.name}`
});
};
</script>
- Send get request
- Same origin policy
- A browser security policy, portocol, hostname, port should be the same
- By default, ajax should obey the same-origin policy and cannot access cross-origin resources
- Solution 1: Use
jsonp
- Logic: the script tag is allowed to bypass CORS restrictions because it was designed to enable developers to include JavaScript code from external sources into their web pages
- JSONP(JSON with Padding) is a technique for making cross-domain requests that bypasses the same-origin policy restrictions implemented by web browsers
- Client: defined a function in the script, let the
src
attribute of the script be the get request url. Server: return function call on response data, the function is the one defined in the Client - An jsonp example
1
2
3
4
5
6
7
8
9// server
router.get("/server-jsonp", (req, res) => {
console.log("received get request");
let data = {
status: 1,
msg: "User exists"
};
res.send(`handle(${JSON.stringify(data)})`);
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- client -->
<script>
let input = document.getElementById("name");
let p = document.querySelector("p");
const handle = (data) => {
input.style.border = "solid 2px red";
p.innerHTML = data.msg;
};
input.onblur = () => {
let username = input.value;
console.log(username);
// Create a script and let it send get request
const script = document.createElement("script");
script.src = "http://149.28.62.112:3000/server-jsonp";
document.body.appendChild(script);
};
</script>
- Solution 2: configure CORS settings on the server side
- Idea: Cross-Origin Resource Sharing (CORS) is an HTTP-header based
mechanism that allows a server to indicate any origins (domain, scheme,
or port) other than its own from which a browser should permit loading
resources
1
2
3
4
5// server code
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "*");
res.setHeader("Access-Control-Allow-Methods", "*");
res.setHeader("Access-Control-Expose-Headers", "*");
- Idea: Cross-Origin Resource Sharing (CORS) is an HTTP-header based
mechanism that allows a server to indicate any origins (domain, scheme,
or port) other than its own from which a browser should permit loading
resources
Promise
- Promise Basics
- The promise represents the completion or failure of an asynchronous operation and its resulting value. It was introduced in ES6. Promise or async/await can be used to prevent callback hell
- Callback hell: nested/ multiple layer of callbacks can cause asynchronouse code become difficult to readand maintain
- An example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42<script>
const rand = (a, b) => Math.ceil(Math.random() * (b - a + 1)) + a - 1;
const rd1 = document.querySelector("#rd1");
const rd2 = document.querySelector("#rd2");
const result = document.querySelector("#result");
rd1.onclick = () => {
setTimeout(() => {
let num = rand(1, 100);
console.log(num);
if (num <= 30) {
result.innerHTML = `You win! Num: ${num}`;
} else {
result.innerHTML = `You loose! Num: ${num}`;
}
}, 100);
};
rd2.onclick = () => {
new Promise((resolve, reject) => {
setTimeout(() => {
let num = rand(1, 100);
console.log(num);
if (num <= 30) {
// pass result value
resolve(num);
} else {
// pass result value
reject(num);
}
}, 100);
}).then(
(value) => {
// succeed
result.innerHTML = `You win! Num: ${value}`;
},
(reason) => {
// reject
result.innerHTML = `You win! Num: ${reason}`;
}
);
};
</script> fs
module promise1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const fs = require("fs");
fs.readFile("./input.txt", (err, data) => {
if (err) {
console.log(err);
return;
}
console.log(data.toString());
});
new Promise((resolve, reject) => {
fs.readFile("./input.txt", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
})
}).then((value) => {
console.log(value.toString());
}, (reason) => {
console.log(reason);
});- ajax promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48<script>
const rd1 = document.querySelector("#rd1");
const rd2 = document.querySelector("#rd2");
const result = document.querySelector("#result");
rd1.onclick = () => {
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://127.0.0.1:9000/server");
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.readyState < 300) {
result.innerHTML = xhr.response;
} else {
result.innerHTML = "Error";
}
} else {
result.innerHTML = "Error";
}
};
};
rd2.onclick = () => {
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://127.0.0.1:9000/server");
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.readyState < 300) {
result.innerHTML = xhr.response;
} else {
result.innerHTML = "Error";
}
} else {
result.innerHTML = "Error";
}
};
}).then(
(value) => {
result.innerHTML = value;
},
(reason) => {
result.innerHTML = `Error ${reason}`;
}
);
};
</script> util.promisify(callback)
, the callback should have the error-first style, i.e.(err, data) => {}
, this method returns a version that returns promises1
2
3
4
5
6const util = require("util");
const fs = require("fs");
const readFilePromise = util.promisify(fs.readFile);
readFilePromise("./input.txt").then((data) => { console.log(data.toString()) }, (msg) => { console.log(msg); });
- Attributes and Execution logic
- Three states:
pending
,resolved/fullfilled
,rejected
pending
=>resolved
/fulfilled
resolve
andreject
can modify PromiseResult- Change promise state:
resolve
,reject
,throw
an error(reject) - Code template
1
2
3new Promise((resolve, reject) => {})
.then((value) => {}, (reason) => {})
.catch((reason) => {}) - Execution order
- If resolve and reject are sync method, they change the promise state first, then is executed afterwards to handle PromiseResult
- If resolve or reject is async method, then waits resolve/reject to change the promise state, and is executed afterwards to handle PromiseResult
- The
then
method- Returns a promise, its result depends on the input of then method. If input callback throws an error, promiseResult of then if error, state is rejected. If input callback does not returns a promise, promiseResult of then is return value of input callback, state is resolved. If input callback returns a callback, it decides the returned promise of the then method
- If you add
catch
to the end of a promise chain, then it will be called when any of the asynchronous function calls fails - How to interrupt promise chain: return a pending promise somewhere
in the chain:
return new Promise(() => {});
- Three states:
- Methods
Promise.resolve()
: if input is not a promise, return resolved promise whose PromiseResult is input; if input is a promise, return a promise that has the same state and result as the input promisePromise.resolve()
: return a rejected promise whose result is input when input is not a promise, or PromiseResult when input is a promisePromise.all(promises)
: if all promises in the input array is resolved then resolved, otherwise rejectedPromise.race(promises)
: return result is the same as the first completed promise
async
andawait
async
function: returns a promise, the promise state and promiseResult depends on the returning value of the function. If returned value is not a promise, returns a resolved promise whose result is the returned value. If returned value is a promise, the returning promise behaves the same as the returned promiseawait RHS
: if RHS is not a promise, returns the value of RHS. If RHS is a promise, await returns the resolved value of the promise, if the promise is rejected, await throws an error, so it should be surrounded with try...catch. await should be inside an async function, but an async function can have no await keyword
ES New Features
- ES6
let
: cannot define twice, has block scope, does not have variable hoistingconst
: use uppercase word- Destructuring assignment: array, object
- String template:
${}
- Object field
1
2
3let x = 1;
let y = 2;
const obj = {x, y}; // same as const obj = {x: x, y: y}; - Object method
1
2
3
4
5
6const obj = {
speak() {
console.log("Speaking");
}
// same as speak: () => {console.log("Speaking")}
} - Arrow function:
this
in arrow function always points to the this object where the function is defined, cannot accessarguments
object - Function argument default value
- Function
...args
arguments: match an array of arguments - Spreading operator:
...arr
- Symbol type: symbol is unique, javascript datatype USONB(undefined,
string, symbol, object, null, number, boolean)
1
2
3
4
5
6let x = Symbol("xx");
ley y = Symbol("xx");
x === y; // false
let a = Symbol.for("yy");
let b = Symbol.for("yy");
a === b; // true - Iterator: type that has
iterator(
Symbol(Symbol.iterator)
) can usefor...of
loop, iterator can be used for user-defined iteration - Generator: a function used for async programming
- Set:
let x = new Set([items])
,s.size
,s.add(x)
,s.delete(x)
,s.has(x)
, can usefor...of
loop on set - Map:
m.set(key, value)
,m.delete(key)
,m.get(key)
, can usefor...of
loop on map - class: instance field/method, static field/method,
extends