A: hi! ~ Hello, everyone, I am YK bacteria 🐷, a front-end microsystem ✨, like to share their small knowledge 🏹, welcome to follow me 😘 ~ [wechat account: YK2012YK2012, wechat official account: ykyk2012]

Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”. This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

RealWorld is a very popular open source project on Github. It will implement login and registration interface, user operation, article operation, add, delete, change and check interface, etc.

Open source project addressGithub.com/gothinkster… In this paper, the source codeGithub.com/yk2012/expe…

Video Express Tutorial (Basics + Practice + Principles)

1. Initialize the project

1.1 Creating a Project

mkdir realworld-api-express
cd .\realworld-api-express\
npm init -y
npm i express
Copy the code

To create the app. Js

const express = require("express");

const app = express();

const PORT = process.env.PORT || 3000;

app.get("/".(req, res) = > {
  res.send("Hello World");
});

app.listen(PORT, () = > {
  console.log(`Server is running at http://localhost:${PORT}`);
});
Copy the code

Start the app

nodemon app.js
Copy the code

test

1.2 Directory Structure

. | -- -- config # config file | -- config. Default. Js | - # controller used to resolve user input, Returns the corresponding results after processing | | - model # data persistence layer, middleware # # for writing middleware | -- the router used oh hey URL routing rules module | | - util # tools -- app. Js # Used for custom initialization at startupCopy the code

1.3 Configuring Common Middleware

1.3.1 Parsing the request body middleware

  • express.json()
  • express.urlencoded()
app.use(express.json())
app.use(express.urlencoded())
Copy the code

1.3.2 Log Output middleware

  • morgan()
npm i morgan
Copy the code

const morgan = require("morgan");

app.use(morgan("dev"));
Copy the code

1.3.3 Provide cross-domain resource request middleware for clients

  • cors()
npm i cors
Copy the code

const cors = require("cors");

app.use(cors());
Copy the code

1.3.4 Mounting the Test Middleware

app.js

const express = require("express");
const morgan = require("morgan");
const cors = require("cors");

const app = express();

app.use(morgan("dev"));
app.use(cors());
app.use(express.json());
app.use(express.urlencoded());

const PORT = process.env.PORT || 3000;

app.get("/".(req, res) = > {
  res.send("Hello World");
});

app.post("/".(req, res) = > {
  console.log(req.body);
  res.send("Hello World");
});

app.listen(PORT, () = > {
  console.log(`Server is running at http://localhost:${PORT}`);
});
Copy the code

2. Route design

With reference to

Github.com/gothinkster…

(1) app. Js

const express = require("express");
const morgan = require("morgan");
const cors = require("cors");
const router = require("./router");

const app = express();

// Configure common middleware
app.use(morgan("dev"));
app.use(cors());
app.use(express.json());
app.use(express.urlencoded());

const PORT = process.env.PORT || 3000;

// Mount the route
app.use("/api", router);

app.listen(PORT, () = > {
  console.log(`Server is running at http://loaclhost:${PORT}`);
});
Copy the code

(2) index. Js routing

const express = require("express");
const router = express.Router();

// User-specific routes
router.use(require("./user"));

// Routes related to user data
router.use("/profiles".require("./profile"));

// article related route
router.use("/articles".require("./article"));

// Label related routes
router.use(require("./tag"));

module.exports = router;
Copy the code

③ User-related routes

const express = require("express");
const router = express.Router();

// Authentication User login
router.post("/users/login".async (req, res, next) => {
  try {
    // Process the request
    res.send("post /users/login");
  } catch(err) { next(err); }});// User Registration
router.post("/users".async (req, res, next) => {
  try {
    // Process the request
    res.send("post /users");
  } catch(err) { next(err); }});// Get Current User Obtains the Current login User
router.get("/user".async (req, res, next) => {
  try {
    // Process the request
    res.send("get /user");
  } catch(err) { next(err); }});// Update User Updates the User
router.put("/user".async (req, res, next) => {
  try {
    // Process the request
    res.send("put /user");
  } catch(err) { next(err); }});module.exports = router;
Copy the code

④ Routes related to profile.js user profiles

const express = require("express");
const router = express.Router();

// Get Profile Obtains user information
router.get("/:username".async (req, res, next) => {
  try {
    // Process the request
    res.send("get /profile/:username");
  } catch(err) { next(err); }});// Follow user Follow user
router.post("/:username/follow".async (req, res, next) => {
  try {
    // Process the request
    res.send("post /profile/:username/follow");
  } catch(err) { next(err); }});// Unfollow user Unfollow user
router.delete("/:username/follow".async (req, res, next) => {
  try {
    // Process the request
    res.send("delete /profile/:username/follow");
  } catch(err) { next(err); }});module.exports = router;
Copy the code

⑤ Article. Js article related routing

const express = require("express");
const router = express.Router();

// List Articles
router.get("/".async (req, res, next) => {
  try {
    // Process the request
    res.send("get /");
  } catch(err) { next(err); }});// Feed Articles
router.get("/feed".async (req, res, next) => {
  try {
    // Process the request
    res.send("get /articles/feed");
  } catch(err) { next(err); }});// Get Article
router.get("/:slug".async (req, res, next) => {
  try {
    // Process the request
    res.send("get /articles/:slug");
  } catch(err) { next(err); }});// Create Article
router.post("/".async (req, res, next) => {
  try {
    // Process the request
    res.send("post /articles");
  } catch(err) { next(err); }});// Update Article
router.put("/:slug".async (req, res, next) => {
  try {
    // Process the request
    res.send("put /articles/:slug");
  } catch(err) { next(err); }});// Delete Article
router.delete("/:slug".async (req, res, next) => {
  try {
    // Process the request
    res.send("delete /articles/:slug");
  } catch(err) { next(err); }});// Add Comments to an Article
router.post("/:slug/comments".async (req, res, next) => {
  try {
    // Process the request
    res.send("post /articles/:slug/comments");
  } catch(err) { next(err); }});// Get Comments from an Article
router.get("/:slug/comments".async (req, res, next) => {
  try {
    // Process the request
    res.send("get /articles/:slug/comments");
  } catch(err) { next(err); }});// Delete Comment
router.delete("/:slug/comments/:id".async (req, res, next) => {
  try {
    // Process the request
    res.send("delete /articles/:slug/comments/:id");
  } catch(err) { next(err); }});// Favorite Article
router.post("/:slug/favorite".async (req, res, next) => {
  try {
    // Process the request
    res.send("post /articles/:slug/favorite");
  } catch(err) { next(err); }});// Unfavorite Article
router.delete("/:slug/favorite".async (req, res, next) => {
  try {
    // Process the request
    res.send("delete /articles/:slug/favorite");
  } catch(err) { next(err); }});module.exports = router;
Copy the code

⑥ Tag. js tag-related routes

const express = require("express");
const router = express.Router();

// Get Tags
router.get("/tags".async (req, res, next) => {
  try {
    // Process the request
    res.send("get /tags");
  } catch(err) { next(err); }});module.exports = router;
Copy the code

3. Extract the controller module

Extract the specific operations that process the request into the controller module

【 Take User as an example 】

controller/user.js

// Authentication User login
exports.login = async (req, res, next) => {
  try {
    // Process the request
    res.send("post /users/login");
  } catch(err) { next(err); }};// User Registration
exports.register = async (req, res, next) => {
  try {
    // Process the request
    res.send("post /users");
  } catch(err) { next(err); }};// Get Current User Obtains the Current login User
exports.getCurrentUser = async (req, res, next) => {
  try {
    // Process the request
    res.send("get /user");
  } catch(err) { next(err); }};// Update User Updates the User
exports.updateUser = async (req, res, next) => {
  try {
    // Process the request
    res.send("put /user");
  } catch(err) { next(err); }};Copy the code

router/user.js

const express = require("express");
const userCtrl = require(".. /controller/user");

const router = express.Router();

// Authentication User login
router.post("/users/login", userCtrl.login);

// User Registration
router.post("/users", userCtrl.register);

// Get Current User Obtains the Current login User
router.get("/user", userCtrl.getCurrentUser);

// Update User Updates the User
router.put("/user", userCtrl.updateUser);

module.exports = router;
Copy the code

4. Configure the unified error handling middleware

middleware/error-handler.js

const util = require("util");

module.exports = () = > {
  return (err, req, res, next) = > {
    res.status(500).json({
      error: util.format(err),
    });
  };
};

Copy the code

app.js

const errorHandler = require("./middleware/error-handler");

// Mount middleware that handles server errors uniformly
app.use(errorHandler());
Copy the code

5. Manage test interfaces in a unified manner

Create a new collection in Postman realWorld-API and create some new folders and specific request actions

Configure base_URL for the development environment and production environmentTest interface {{base_URL}}

Database of 6.

For database related knowledge, see [MongoDB] Database installation and basic operations – Mongoose – Add, Delete, Modify, and Check

6.1 installation mongoose

npm i mongoose
Copy the code

Start the MongoDB Compass

6.2 Connecting a Database

Configure the default database address config/config.default.js

/** * Default configuration */
module.exports = {
  dbURI: "mongodb://localhost:27017/realworld"};Copy the code

model/index.js

const mongoose = require("mongoose");
const { dbURI } = require(".. /config/config.default");

// Connect to MongoDB database
mongoose.connect(dbURI, {
  useNewUrlParser: true.useUnifiedTopology: true});const db = mongoose.connection;
// When the connection fails
db.on("error".(err) = > {
  console.log(MongoDB database connection failed!, err);
});
// When the connection succeeds
db.once("open".function () {
  console.log("MongoDB database connection successful!");
});
Copy the code

App.js introduces the connection database

require("./model");
Copy the code

6.3 Design the data model

model/user.js userSchema

const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
  username: {
    type: String.required: true,},email: {
    type: String.required: true,},password: {
    type: String.required: true,},bio: {
    type: String.default: null,},image: {
    type: String.default: null,},createdAt: {
    type: Date.default: Date.now,
  },
  updateAt: {
    type: Date.default: Date.now,
  },
});

module.exports = userSchema;
Copy the code

6.4 Exporting the Data Model

model/index.js

const mongoose = require("mongoose");
const { dbURI } = require(".. /config/config.default");

// Connect to MongoDB database
mongoose.connect(dbURI, {
  useNewUrlParser: true.useUnifiedTopology: true});const db = mongoose.connection;
// When the connection fails
db.on("error".(err) = > {
  console.log(MongoDB database connection failed!, err);
});
// When the connection succeeds
db.once("open".function () {
  console.log("MongoDB database connection successful!");
});

// Organize the export model to look at the class
module.exports = {
  User: mongoose.model('User'.require('./user')),
  Article: mongoose.model('Article'.require('./article'))}Copy the code

6.5 Inserting Data into the Database

const { User } = require(".. /model");

// User Registration
exports.register = async (req, res, next) => {
  try {
    let user = new User(req.body.user);
    // Save to database
    await user.save();
    / / to json
    user = user.toJSON();
    // Delete password attributes
    delete user.password;
    // 4. The response is successfully sent and user data is returned
    res.status(201).json({
      user,
    });
  } catch(err) { next(err); }};Copy the code

6.6 test

[Registered Users]

6.7 [Optimization] Extract the general data model

Extract the common data model to create a separate file base-model.js, and then import it where needed

model/base-model.js

module.exports = {
  createdAt: {
    type: Date.default: Date.now,
  },
  updateAt: {
    type: Date.default: Date.now,
  },
};
Copy the code

7. Data verification

7.1 Interface Design Procedure

  1. Gets the request body data
  2. Data Verification 2.1 Basic Data Verification 2.2 Service Data Verification
  3. If the verification succeeds, the data is saved to the database
  4. Send successful response

7.2 Data Verification Problems

Mongodb validates the data when it’s being inserted into the database and we actually need to do some validation of the data before it’s being inserted

You can use node.js resources to do this. You can follow this repository, which has a great collection of Node.js resources

awesome-nodejs Github.com/sindresorhu…There are some excellent libraries for data validation

Here we use Express-Validator, which is a Validator-based Express middleware (wrapper for Validator)Github.com/express-val… The installation

npm i express-validator
Copy the code

The documentexpress-validator.github.io/docs/

7.3 use express – the validator

Add middleware to router/user.js

const { body, validationResult } = require("express-validator");
const { User } = require(".. /model");

// User Registration
router.post(
  "/users"[// 1. Configure authentication rules
    body("user.username")
      .notEmpty()
      .withMessage("User name cannot be empty")
      .custom(async (value) => {
        // Query the database to check whether the data exists
        const user = await User.findOne({ username: value });
        if (user) {
          return Promise.reject("User already exists");
        }
      }),
    body("user.password").notEmpty().withMessage("Password cannot be empty."),
    body("user.email")
      .notEmpty().withMessage("Mailbox cannot be empty.")
      .isEmail().withMessage("Email format is incorrect")
      .bail() // Do not proceed if there is an error
      .custom(async (value) => {
        // Query the database to check whether the data exists
        const user = await User.findOne({ email: value });
        if (user) {
          return Promise.reject("Mailbox already exists"); }}),].(req, res, next) = > {
    // 2. Determine the verification result
    const errors = validationResult(req);
    // Verification failed
    if(! errors.isEmpty()) {return res.status(400).json({ errors: errors.array() });
    }
    // Pass the verification
    next();
  },
  userCtrl.register
); // 3. After verification, perform specific controller processing
Copy the code

Data error

successfulData duplication

7.4 Extracting and verifying middleware modules

There are so many validation rules and judgment validation rules in the routing code that the code is very messy. We should separate these validation rules into a middleware

Middleware /validate.js processes the validation results and extracts them from the Validate middleware

const { validationResult } = require("express-validator");

// Parallel processing
// Exposes a function that receives validation rules and returns a function
module.exports = (validations) = > {
  return async (req, res, next) => {
    await Promise.all(validations.map((validation) = > validation.run(req)));

    const errors = validationResult(req);
    if (errors.isEmpty()) {
      return next();
    }

    res.status(400).json({ errors: errors.array() });
  };
};
Copy the code

Validator /user.js extracts [validation rules]

const { body } = require("express-validator");
const validate = require(".. /middleware/validate");
const { User } = require(".. /model");

exports.register = validate([
  // 1. Configure authentication rules
  body("user.username")
    .notEmpty().withMessage("User name cannot be empty")
    .custom(async (value) => {
      // Query the database to check whether the data exists
      const user = await User.findOne({ username: value });
      if (user) {
        return Promise.reject("User already exists");
      }
    }),
    
  body("user.password").notEmpty().withMessage("Password cannot be empty."),
  
  body("user.email")
    .notEmpty().withMessage("Mailbox cannot be empty.")
    .isEmail().withMessage("Email format is incorrect")
    .bail() // Do not proceed if there is an error
    .custom(async (value) => {
      // Query the database to check whether the data exists
      const user = await User.findOne({ email: value });
      if (user) {
        return Promise.reject("Mailbox already exists"); }}));Copy the code

Routing simplifies router/user.js

const userValidator = require(".. /validator/user");

// User Registration
router.post("/users", userValidator.register, userCtrl.register); 
Copy the code

8. Password encryption

Passwords are stored in plain text in the database and should be stored in ciphertextPlain text is converted to ciphertext using MD5 algorithm

const crypto = require("crypto");

// Get the hash algorithm crypto supports
console.log(crypto.getHashes());

/ / use
const reslut = crypto.createHash("md5").update("hello").digest("hex");

// Get the result
console.log(reslut)
Copy the code

If the plaintext string is encrypted by MD5, the result is the same. You can add a plaintext private key or perform md5 encryption twice

util/md5.js

const crypto = require("crypto");

module.exports = (str) = > {
  return crypto
    .createHash("md5")
    .update("yk" + str) // Add an obfuscation string for better security
    .digest("hex");
};
Copy the code

Configure model/user.js in the model

const mongoose = require("mongoose");
const baseModle = require("./base-model");
const md5 = require(".. /util/md5");

const userSchema = newmongoose.Schema({ ... baseModle,username: {
    type: String.required: true,},email: {
    type: String.required: true,},password: {
    type: String.required: true.set: (value) = > md5(value),
  },
  bio: {
    type: String.default: null,},image: {
    type: String.default: null,}});module.exports = userSchema;
Copy the code

Add one more, so that the message returned does not contain the password

password: {
  type: String.required: true.set: (value) = > md5(value),
  select: false,},Copy the code

Also remove the Password property from the controller so it doesn’t return

// User Registration
exports.register = async (req, res, next) => {
  try {
    let user = new User(req.body.user);
    // Save to database
    await user.save();
    user = user.toJSON();
    delete user.password;
    // 4. The response is sent successfully
    res.status(201).json({
      user,
    });
  } catch(err) { next(err); }};Copy the code

9. Login interface

9.1 Verifying Login Information

Validator /user.js validates an array with multiple validates before one of them passes

exports.login = [
  validate([
    body("user.emil").notEmpty().withMessage("Mailbox cannot be empty."),
    body("user.password").notEmpty().withMessage("Password cannot be empty."),]),// Verify that the user exists
  validate([
    body("user.emil").custom(async (email, { req }) => {
      const user = await User.findOne({ email }).select([
        "email"."password"."username"."bio"."image",]);// Query the database to check whether the data exists
      if(! user) {return Promise.reject("User does not exist");
      }
      // By mounting the data to the request object, subsequent middleware can also be used directly, eliminating the need for repeated queriesreq.user = user; }),]),// Verify the password is correct
  validate([
    body("user.password").custom(async (password, { req }) => {
      if(md5(password) ! == req.user.password) {return Promise.reject("Password error"); }}),]),];Copy the code
// Authentication User login
router.post("/users/login", userValidator.login, userCtrl.login);
Copy the code

9.2 JWT-BASED Identity Authentication

JSON Web Token (JWT) is the most popular cross-domain authentication solution

Cross-domain authentication problem

Internet services are inseparable from user authentication. The general process is as follows:

  1. The user sends the username and password to the server
  2. After the authentication, the server saves relevant data in the current session, such as user role, login time, and so on
  3. The server returns a session_ID to the user and writes the user’s Cookie
  4. The session_id is passed back to the server via cookies with each subsequent request from the user
  5. The server receives the session_id, finds the previously saved data, and learns the user’s identity

The problem with this model is that it does not scale well. If it is a server cluster or a cross-domain service-oriented architecture, session data is required to be shared and each server can read sessions.

For example, website A and website B are affiliated services of the same company. Now the requirement, as long as the user login in one of the websites, then visit another website will automatically login, how to achieve?

One solution is to persist session data to a database or other persistence layer. Upon receiving the request, the various services request data from the persistence layer. The advantage of this scheme is the clear structure, but the disadvantage is the large amount of engineering. In addition, if the persistence layer fails, it will fail in a single point.

The other option is that the server does not store session data, all data is stored on the [client] side, and each request is sent back to the server. JWT is an example of such a scheme.

JWT principle

The JWT principle is that after the server authenticates, it generates a JSON object and sends it back to the user, as shown below.

{
	"Name":"Zhang"."Role":"Administrator"."Due Time": "0:00 on July 1, 2021."
}
Copy the code

In the future, the user will send back this JSON object when communicating with the server. The server identifies the user solely on this object. To prevent users from tampering with the data, the server generates this object with a signature (see below).

The server does not hold any session data, that is, the server becomes stateless, making it easier to scale.

JWT data structure

The actual JWT looks like thisIt’s a long string with dots in the middle.Divided into three parts. Note that there is no line break inside JWT, but it is written here as a few lines for demonstration purposes.

The three parts of JWT are as follows:

  • Header
  • Payload
  • Signature (= Signature)

Let’s write it as a line

Header.Payload.Signature
Copy the code

Header

The Header section is a JSON object that describes the metadata of the JWT, usually as follows

{
	"alg": "HS256"."type": "JWT"
}
Copy the code
  • algAttribute indicates the algorithm of the signature. The default value is HMAC SHA256(HS256).
  • typProperty represents the type of the token. JWT tokens are written as JWT.

Finally, convert the above JSON object into a string using the Base64URL algorithm (see below).

Payload

The Payload part is also a JSON object that stores the data that needs to be transmitted. JWT specifies seven official fields to choose from.

  • iss(issuer) : issued by people
  • expExpiration time: indicates the expiration time
  • sub(a subject) : theme
  • aud(on) : the audience
  • nbf(Not Before): Effective time
  • iatLssued: Issue time
  • jtiJWT (ID) : serial number

In addition to official fields, you can also define private fields

{
	"sub": "1234323432"."name": "YK"."admin": true
}
Copy the code

[Note] JWT is not encrypted by default, do not put secret information in this section

The JSON object is also converted to a string using the Base64URL algorithm

jwt.io/

Signature

The Signature section is a Signature to the first two sections, preventing data tampering.

First, you need to specify a secret. This key is known only to the server and cannot be disclosed to users. Then, using the signature algorithm specified in the Header (HMAC SHA256 by default), generate the signature as follows.

HMACSHA256(
	base64UrlEncode(header) + "." +
	base64UrlEncode(payload),
	secret)
Copy the code

After calculating the Signature, form the Header, Payload, and Signature parts into a string with dots (.) between each part. Delimit, and it can be returned to the user.

In JWT, the body of the message is transparent, and using a signature ensures that the message is not tampered with. However, data encryption cannot be implemented.

Base64URL

As mentioned earlier, Header and Payload are serialized using Base64URL. This algorithm is basically similar to the Base64 algorithm, with some minor differences.

JWT as a token may be placed in a URL in some cases (such as api.example.com/?token=xxx). Base64 has three characters +, /, and =, which have special meanings in URLS and are therefore replaced := is omitted, + is replaced with -, and/is replaced with _. This is the Base64URL algorithm.

JWT usage mode

The client receives the JWT returned by the server, which can be stored in cookies or localStorage.

After that, the client takes this JWT with it every time it communicates with the server. You can put it in a Cookie and send it automatically, but that’s not cross-domain, so it’s better to put it in the HTTP request header Authorization field.

Authorization: Bearer <token>
Copy the code

Alternatively, the JWT is placed in the data body of the POST request when it is cross-domain

Several characteristics of JWT

  1. JWT is not encrypted by default, but it can be encrypted. Once the original Token is generated, it can be encrypted again with the key.
  2. Secret data cannot be written to the JWT without encryption.
  3. JWT can be used not only for authentication, but also for information exchange. Using JWT effectively can reduce the number of times the server queries the database.
  4. The biggest drawback of JWT is that since the server does not store session state, there is no way to invalidate a token or change the token’s permissions during use. That is, once a JWT is issued, it remains valid until expiration, unless the server deploys additional logic.
  5. The JWT itself contains authentication information, and if it is disclosed, anyone can gain full access to the token. To reduce theft, JWT expiration dates should be shorter. For some important permissions, the user should be authenticated again.
  6. To reduce theft, JWT should not use HTTP for explicit code transport but HTTPS for transport.

JWT’s solution

jwt.io/

Use JWT in Node.js

Github.com/auth0/node-…

npm install jsonwebtoken
Copy the code

Basic use has synchronous and asynchronous two ways, add a third callback function, is asynchronous execution

const jwt = require("jsonwebtoken");

/ / generated JWT
jwt.sign(
  {
    name: "YK",},"abcdykyk".// Generate the token asynchronously
  (err, token) = > {
    if (err) return console.log("Token generation failed");
    console.log("Token generation succeeded:", token); });/ / verification JWT
jwt.verify(
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiWUsiLCJpYXQiOjE2MjQ5NTgxNTl9.PcFPUDaqL_HHw7bctKcyI-CnCgwNgOGZwe7tYPtAj _Y"."abcdykyk".(err, res) = > {
    if (err) return console.log("Token authentication failed");
    console.log("Token authentication succeeded:", res); });Copy the code

9.3 Generating a Token and Sending it to a Client

JWT asynchrony is not in the promise form and can be converted to the Promise form

util /jwt.js

const jwt = require("jsonwebtoken");
const { promisify } = require("util");

/ / parsing
exports.sign = promisify(jwt.sign);

/ / verification
exports.verify = promisify(jwt.verify);

// Direct parsing without validation
// exports.decode = jwt.decode();
exports.decode = promisify(jwt.decode)
Copy the code

Generate a unique string with the UUIDwww.uuidgenerator.net/

Generate a unique key from the UUID

ca8b3b61-6344-46fc-83ee-d81c0ca35480
Copy the code

Set config/config.default.js in the default configuration

/** * Default configuration */
module.exports = {
  dbURI: "mongodb://localhost:27017/realworld".jwtSecret: "ca8b3b61-6344-46fc-83ee-d81c0ca35480"};Copy the code

controller/user.js

const jwt = require(".. /util/jwt");
const { jwtSecret } = require(".. /config/config.default");

// Authentication User login
exports.login = async (req, res, next) => {
  try {
    // Process the request
    // get user information [mongoSSE data object converted to JSON data object]
    const user = req.user.toJSON();
    / / token is generated
    const token = await jwt.sign(
      {
        userId: user._id,
      },
      jwtSecret
    );
    // Remove the password attribute
    delete user.password;
    // Send a successful response (user information containing token)
    res.status(200).json({ ... user, token, }); res.send("post /users/login");
  } catch(err) { next(err); }};Copy the code

9.4 Setting the JWT Expiration Time

/ / token is generated
const token = await jwt.sign(
  {
    userId: user._id,
  },
  // Set the token expiration time, in seconds
  jwtSecret,
  {
    expiresIn: 60 * 60 * 24});Copy the code

The interface test tool is automatically configured to add token data

10. Authentication middleware

Obtaining the interface between the current user and the updated user requires authentication of the token

middleware/auth.js

const { verify } = require(".. /util/jwt");
const { jwtSecret } = require(".. /config/config.default");
const { User } = require(".. /model");

module.exports = async (req, res, next) => {
  // Get token data from the request header
  let token = req.headers.authorization;
  // Verify that the token exists
  token = token ? token.split("Token ") [1] : null;
  // If it does not exist, send response 401 to end the response
  if(! token) {return res.status(401).end();
  }
  try {
  	// Verify the token is valid
    const decodedToken = await verify(token, jwtSecret);
    // console.log('decodedToken:',decodedToken);
    // Mount the user information to the request object
    req.user = await User.findById(decodedToken.userId);
    next();
  } catch (err) {
    return res.status(401).end();
  }
  // If yes, read the user information, mount it to the reQ request object, and continue
};
Copy the code

router/user.js

const auth = require(".. /middleware/auth");

// Get Current User Obtains the Current login User
router.get("/user", auth, userCtrl.getCurrentUser);
Copy the code

controller/user.js

// Get Current User Obtains the Current login User
exports.getCurrentUser = async (req, res, next) => {
  try {
    // Process the request
    res.status(200).json({
      user: req.user,
    });
  } catch(err) { next(err); }};Copy the code

Authentication successAuthentication failedThe Router can configure auth middleware for all interfaces that require authentication

11. Article related interface

11.1 Creating an Article

① Define the data model

Articles and the authors associated with the mongoose populate mongoosejs.com/docs/popula…

model/article.js

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const baseModle = require("./base-model");

const articleSchema = newmongoose.Schema({ ... baseModle,title: {
    type: String.required: true,},description: {
    type: String.required: true,},body: {
    type: String.required: true,},tagList: {
    type: [String].default: null,},favoritesCount: {
    type: Number.default: 0,},author: {
    type: Schema.Types.ObjectId,
    ref: "User".required: true,}});module.exports = articleSchema;
Copy the code

② Data verification

validator/article.js

const { body } = require("express-validator");
const validate = require(".. /middleware/validate");

exports.createArticle = validate([
  body("article.title").notEmpty().withMessage("Article title cannot be empty."),
  body("article.description").notEmpty().withMessage("The abstract cannot be empty."),
  body("article.body").notEmpty().withMessage("The content of the article cannot be empty."),]);Copy the code

③ Processing requests

controller/article.js

Mongoosejs.com/docs/popula…

const { Article } = require(".. /model");

// Create Article
exports.createArticle = async (req, res, next) => {
  try {
    // Process the request
    const article = new Article(req.body.article);
    
    // Get the id attribute from the user object resolved through authentication
    article.author = req.user._id;
    // Map the data to User and do the following
    article.populate("author").execPopulate();
    
    await article.save();
    res.status(201).json({
      article,
    });
  } catch(err) { next(err); }};Copy the code

(4) the routing

router/article.js

// Create Article creates the Article
router.post(
  "/",
  auth,
  articleValidator.createArticle,
  articleCtrl.createArticle
);
Copy the code

(5) test

11.2 Obtaining Articles

① Data verification

validator/article.js

const mongoose = require("mongoose");

exports.getArticle = validate([
  param("articleId").custom( async (value) => {
    if(! mongoose.isValidObjectId(value)) {return Promise.reject("Article ID type error"); }}));Copy the code

② Processing requests

controller/article.js

// Get Article
exports.getArticle = async (req, res, next) => {
  try {
    // Process the request
    const article = await Article.findById(req.params.articleId).populate("author");
    if(! article) {return res.status(404).end();
    }
    res.status(200).json({
      article,
    });
  } catch(err) { next(err); }};Copy the code

(3) the routing

router/article.js

// Get Article
router.get("/:articleId", articleValidator.getArticle, articleCtrl.getArticle);
Copy the code

11.3 Querying Articles

When requested, different parameters can be sent and different data can be responded to

11.3.1 Return to all articles

// List Articles
exports.listArticles = async (req, res, next) => {
  try {
    // Process the request
    const articles = await Article.find();
    const articlesCont = await Article.countDocuments();
    res.status(200).json({
      articles,
      articlesCont,
    });
    res.send("get /articles/");
  } catch(err) { next(err); }};Copy the code

11.3.2 Data paging

// List Articles
exports.listArticles = async (req, res, next) => {
  try {
    // Process the request
    
    // Parse the data parameters and set the default values
    const { limit = 20, offset = 0 } = req.query;
    const articles = await Article.find()
      .skip(+offset) // How many lines to skip
      .limit(+limit); // How many items do I need
      
    const articlesCont = await Article.countDocuments();
    res.status(200).json({
      articles,
      articlesCont,
    });
    res.send("get /articles/");
  } catch(err) { next(err); }};Copy the code

Skip the first two pieces of data and take one piece of data

11.3.3 Filtering labels


// List Articles
exports.listArticles = async (req, res, next) => {
  try {
    // Process the request

    // Parse the data parameters and set the default values
    const { limit = 20, offset = 0, tag } = req.query;

    // Define a filter object
    const filter = {};
    if (tag) {
      filter.tagList = tag;
    }

    const articles = await Article.find(filter)
      .skip(+offset) // How many lines to skip
      .limit(+limit); // How many items do I need
    const articlesCont = await Article.countDocuments();
    res.status(200).json({
      articles,
      articlesCont,
    });
    res.send("get /articles/");
  } catch(err) { next(err); }};Copy the code

11.3.4 Screening authors

// List Articles
exports.listArticles = async (req, res, next) => {
  try {
    // Process the request

    // Parse the data parameters and set the default values
    const { limit = 20, offset = 0, tag, author } = req.query;

    // Define a filter object (for query)
    const filter = {};
    if (tag) {
      filter.tagList = tag;
    }
    if (author) {
      const user = await User.findOne({ username: author });
      filter.author = user ? user._id : null;
    }

    const articles = await Article.find(filter)
      .skip(+offset) // How many lines to skip
      .limit(+limit); // How many items do I need
    const articlesCont = await Article.countDocuments();
    res.status(200).json({
      articles,
      articlesCont,
    });
    res.send("get /articles/");
  } catch(err) { next(err); }};Copy the code

11.3.5 Sorting Data

const articles = await Article.find(filter)
  .skip(+offset) // How many lines to skip
  .limit(+limit) // How many items do I need
  .sort({        / / sorting
    // -1: in reverse order 1: in ascending order
    createdAt: -1});Copy the code

11.4 Updating an Article

Package middleware to verify whether ID is valid

validate.js

const { validationResult, buildCheckFunction } = require("express-validator");
const { isValidObjectId } = require("mongoose");

// Check whether the ID is a valid ObjectID
exports.isValidObjectId = (location, fields) = > {
  return buildCheckFunction(location)(fields).custom(async (value) => {
    if(! isValidObjectId(value)) {return Promise.reject("ID is not a valid ObjectID"); }}); };Copy the code

validator/article.js

exports.getArticle = validate([
  validate.isValidObjectId(["params"]."articleId"),
  // param("articleId").custom(async (value) => {
  // if (! mongoose.isValidObjectId(value)) {
  // return promise.reject (" article ID type error ");
  / /}
  // }),
]);

exports.updateArticle = validate([
  validate.isValidObjectId(["params"]."articleId"),]);Copy the code

router/article.js

// Update Article
router.put(
  "/:articleId",
  auth,
  articleValidator.updateArticle,
  articleCtrl.updateArticle
);
Copy the code

② 404 and 403 verification

Also verify the existence of the article not found 404 not found

The article found was created by the current login user

validator/article.js

exports.updateArticle = [
  // Verify whether the ID is ObjectID
  validate([validate.isValidObjectId(["params"]."articleId")),// Verify that the article exists
  async (req, res, next) => {
    const articleId = req.params.articleId;
    const article = await Article.findById(articleId);
    req.article = article;
    if(! article) {return res.status(404).end();
    }
    next();
  },
  // Determine whether the author of the modified article is the current login user
  async (req, res, next) => {
    console.log(typeof(req.user._id), typeof(req.article.author));// object object
    if(req.user._id.toString() ! == req.article.author.toString()) {return res.status(403).end(); } next(); },];Copy the code

③ Realize the response to update the article

controller/article.js

// Update Article
exports.updateArticle = async (req, res, next) => {
  try {
    const article = req.article;
    const bodyArticle = req.body.article;
    article.title = bodyArticle.title || article.title;
    article.description = bodyArticle.description || article.description;
    article.body = bodyArticle.body || article.body;
    await article.save();
    res.status(200).json({
      article,
    });
  } catch(err) { next(err); }};Copy the code

11.5 Deleting Articles

① Verification rules

validator/article.js

exports.deleteArticle = exports.updateArticle;
Copy the code

(2) routing

router/article.js

// Delete Article
router.delete(
  "/:articleId",
  auth,
  articleValidator.deleteArticle,
  articleCtrl.deleteArticle
);
Copy the code

③ Process the request operation

controller/article.js

// Delete Article
exports.deleteArticle = async (req, res, next) => {
  try {
    console.log(req.article);
    const article = req.article;
    await article.remove();
    res.status(204).end();
  } catch(err) { next(err); }};Copy the code

Video Express Tutorial (Basics + Practice + Principles)