Art template + Express + mongodb small project

Preface

In order to experience the content of front-end and back-end interaction, I found art template + Express + mongodb( Code GitHub - > https://github.com/molifenge/selfblog )This simple small project practice hands, the page is extremely simple, do not like to spray. This project is mainly developed by using the front-end engine art template + Express + mongodb. This article will explain the technology and some details of the project.

1, Renderings

Front interface: article list page + article details page
1. Front page (including paging function)

2. Realization of article details page + comment function

Background interface: login page + user list page + article list page
1. User login:

2. User add delete (paging function)

3. User modification function (unable to modify if password comparison fails)

4. Article adding function (including picture upload)

5. Article modification and deletion function

Two, function

Function completed

  1. User login
  2. Add, modify and delete users (including password verification and data paging functions)
  3. Article addition, modification and deletion (including picture upload and data paging functions)
  4. Front page display of article list
  5. Article details page
  6. Article comment function

Looking forward to optimization or realization

  1. User registration function
  2. When the article is modified, the original date is displayed by default
  3. The implementation of adaptive layout of front page list
  4. Front desk article list page beautification
  5. Comment function editor to Markdown editor

3, Main technologies

  1. Front end engine art template + express art template
  2. Express
  3. MongoDB
  4. Body parser plug-in (processing post parameter)
  5. Formable plug-in (parsing uploaded file data)
  6. bcrypt plug-in (encrypt password)
  7. Mongoose sex page module (implementation of paging function)
  8. Joi plug in (data validation)
  9. Express session (used to store the sessionid of the login user)

4, Express+MongoDB

Use of Express

4.1 Express

Express is a web application development framework based on Node platform. It provides a series of powerful features to help us create various web applications.

First, we need to install Express.

npm i express

Then, we use express to create a web server for later page interaction:

//Introducing the express framework
const express = require('express');
//Create web server
const app = express();
//Listen to port 8080, i.e. you can access the files in this project through localhost:8080 /
app.listen(8080):

4.2 Express Middleware

Middleware is a bunch of methods. Node provides middleware that can intercept requests, respond to them, or deliver them to the next middleware.

Here is a schematic diagram of middleware I have seen, which is very clear:

Middleware consists of two parts: middleware method and request processing function. Middleware methods are provided by Express to intercept requests; request processing functions are written by programmers to process requests.
For example, app.use(path,function(req,res,next)). The first parameter path is the path to be requested, and the second parameter is the corresponding request processing function. This function includes three parameters, namely req, res and next. Req is a request; res is a response; next is a method, because we may use more than one Middleware in the program. The next method is used to release the request and jump to the next middleware. This parameter is written when it is needed. If it is not used, it can not be written.

In this project, I mainly used several middleware: app.use(), app.get(), app.port(), and the built-in middleware express.static(), which have different functions.
app.use(path,function(req,res,next)) -- when a request is made to the page path (no matter what request is), call the latter request processing function.
app.get(path,function(req,res,next)) -- when a get request is sent to the page path, the next request processing function is called.
app.port(path,function(req,res,next)) -- when a get request (form submission data) is sent to the page path, the later request processing function is called.
App. Use (express. Static (path. Join ('dirname, 'public))) -- open static resource file, so you can use url to access static resources such as pictures and css in the project (public page is used to store static resources).

4.3 routing documents in the project

In the project, I put all the front-end pages in the views folder, and all the routing processing logic in the route folder. Then, according to the current background of the item, further distinguish the two folders admin (background) and home (foreground).

In order to make the dependency between files more clear, all the routing processing functions are encapsulated into a routing processing module, and then the whole module is substituted. According to the front and back functions, the routing processing is divided into two main files. admin.js is responsible for processing the request for the back system page, and home.js is responsible for the request for the front page. So write in app.js as follows:

//Import two main routing processing logic
const admin = require('./route/admin');
const home = require('./route/home');
//When accessing localhost:8080/admin, a request will be sent to admin. The same as above for home
app.use('/admin',admin);
app.use('/home',home);

Then, in admin.js, create a route through express.Router(), and then use middleware to intercept page requests and corresponding route processing modules. Finally, don't forget to export an entire module, module.exports = admin. The file reads:

// Back-stage management
//Reference express framework
const express = require('express');
//Create a blog presentation page route
const admin = express.Router();

//Render login page
admin.get('/login',require('./admin/loginPage'));
//Receive and judge the data of login page
admin.post('/login',require('./admin/login'));

//Create user list route
admin.get('/user',require('./admin/userPage'));

//Implement exit function
admin.get('/logout',require('./admin/logout'));

// 1. User management
// Create user edit page route
admin.get('/user-edit',require('./admin/user-edit'));
//Create a route to implement user add function
admin.post('/user-edit',require('./admin/user-edit-fn'));
// User modify route
admin.post('/user-modify',require('./admin/user-modify'));
// Delete user function route
admin.get('/delete',require('./admin/delete'));

// 2. Article management
// Article list page routing
admin.get('/article',require('./admin/article'));
// Article editing page routing
admin.get('/article-edit',require('./admin/article-edit'));
// Article add function route
admin.post('/article-edit',require('./admin/article-add'));
//User modify route
admin.post('/article-modify',require('./admin/article-modify'));
// Delete article feature route
admin.get('/article-delete',require('./admin/article-delete'));

module.exports = admin;

home.js is not listed here. You can go to Github to check it.

Use of MongoDB

4.5 MongoDB

MongoDB is a database, which has many advantages. One of them is that MongoDB does not need to explicitly create a database. If the database being used does not exist, MongoDB will automatically create it.

Mongoose, a third-party module, is required to operate MongoDB. Many functions to be used (including normal addition, deletion, query and modification) are in mongoose.
First, we need to install MongoDB. At the command line, enter:

npm i mongoose

Then start the database:

net start mongodb

To stop a database connection:

net stop mongodb

Finally, connect to the database.

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/playground')
	.then(() => console.log('Database connection successful'))
	.catch(err => console.log('Database connection failed',err));

be careful!!! MongoDB has no account and password by default. If you want to know how to access the database with an account and password, give directions – > MongoDB sets the user name and password https://www.jianshu.com/p/237a0c5ad9fa.

4.5 database tables used in the project

I put the logic of creating database tables and connecting databases required by this project in the model folder. According to the requirements, this project needs to build three tables, namely user, article and comment, corresponding to user.js, article.js and comment.js. The corresponding fields can be viewed directly in the code.

4.6 creation set, addition, deletion, query and modification of mongodb

4.6.1 create set

There are two steps to create a set: 1. Set rules for the set; 2. Create a set.
Take user.js as an example:

//1. Create a set rule
const UserSchema = new mongoose.Shema({
	username:{
		type:String,//Type - value type
        required:true,//require - true is required
        minlength:2,//Minimum string length
        maxlength:20//Maximum string length
	},//User name
	email:{
		type:String,
        // unique is true to ensure that the email address is not duplicate, because this is used as the login name
        unique:true,
        required:true
	},//mailbox
	password:{
		type:String,
        required:true
	},//Password
	role:{
		type:String,
        required:true
	},//Role, admin is super administrator, normal is normal user
	state:{
		type:Number,
        default:0//The default value is 0
	}//Status, 0 -- enabled, 1 -- disabled
});
//2. Create set (table) User
const User = mongoose.model('User',userSchema);
//Finally, remember to export the collection, otherwise other routing modules cannot use it
module.exports = User;

Pay attention here!! After the collection is created, we don't necessarily see it in the database. A collection can only be created explicitly if it has data in it.

4.6.2 add, delete, check and modify

The following are some database addition, deletion, query and modification statements used in the project:

function createUser(){ //Add a document.
    const salt = await bcrypt.genSalt();
    const pass = await bcrypt.hash('123456',salt);
    const user = await  User.create({
        username:'iteheima',
        email:'123456@qq.com',
        password:pass,
        role:'admin',
        state:0
    });
User.find().then(result => console.log(result)); //Query all documents in User
User.findOne({_id:id}); //Query the document whose id value is id (condition) in the User table.
User.findOneAndDelete({_id:id}); //Delete a document with id
User.updateOne({_id:id},{ //Update the document whose id value is id. see the second parameter for the updated data
	username:username,
    email:email,
    role:role,
    state:state
});

5, Art template

Front end rendering is to render data to front-end pages. Generally speaking, there are three methods:

  1. JS primitive syntax rendering
  2. Front end rendering engines such as art template
  3. Vue template syntax.

Here I use the art template template engine.

Art template is a simple and super fast template engine. It uses scope pre declaration technology to optimize the template rendering speed, so as to achieve the running performance close to the JavaScript limit, and supports both NodeJS and browsers.

First, install art template. In order to better support the use of art template in Express, I also installed Express art template.

npm i art-template express-art-template

Then, make some configuration:

//Import art template
const template = require('art-template');
const path = require('path');
//Tells the browser which template engine to use when rendering a module with an art suffix
app.engine('art',require('express-art-template'));
//Tell the express framework where the template is located
app.set('views',path.join(__dirname,'views'));
//Tell the default suffix of the express framework template
app.set('view engine','art');

In this way, we can use the art template template syntax for front-end rendering. Next, we will talk about the application of art template in this project.
First of all, you should know that there are some common modules in the project, such as the head, tail, navigation bar and so on. In order to reduce code redundancy and the amount of code we write, art template provides two methods: template inheritance and sub template application. Then I put some public modules in the corresponding common folder.
The common folder directory is as follows:

layout.art: page skeleton, used to place common parts of the page, such as some files such as < HTML >, < head >, < body >.

<!-- Template inheritance -->
<!-- layout.art-The page skeleton is used to place the tags and resources used by all pages, including some<html>,<head>,<body>Etc. fixed label-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>Blog - Content Manager</title>
    <link rel="stylesheet" href="/admin/lib/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/admin/css/base.css">
    <!--this block Import the corresponding template's link Part-->
    {{block 'link'}}{{/block}}
</head>

<body>
	<!--this block Import the corresponding template's main Section, that is, the main part of the page-->
	{{block 'main'}} {{/block}}
	<script src="/admin/lib/jquery/dist/jquery.min.js"></script>
	<script src="/admin/lib/bootstrap/js/bootstrap.min.js"></script>
    <script src="/admin/js/common.js"></script>
    <!--this block Import the corresponding template's script Part, i.e. calculation logic part-->
	{{block 'script'}} {{/block}}
</body>
</html>

header.art and aside.art: the common part of the page, which is the header and side navigation bar.

Then, inherit layout.art from the corresponding page and insert the sub templates header.art and aside.art.
For example, user.art (see the code for details):

<!--inherit layout.art,Soon to each block Put it in the corresponding layout.art Corresponding position-->
{{extend './common/layout.art'}}

{{block 'main'}}
    <!-- The relative path of the sub template is the current file because it is parsed by the template engine instead of the browser -->
    <!--Insert common part sub template-->
    {{include './common/header.art'}}
    <!-- Main content -->
    <div class="content">
    	{{include './common/aside.art'}}
        <div class="main">
        	<!-- Classified headings -->
            <div class="title">
                <h4>user{{userInfo.username}}</h4>
                <span>find{{count}}Individual users</span>
                <a href="/admin/user-edit" class="btn btn-primary new">New users</a>
            </div>
            <!-- /Classified headings -->
            <!-- Content list -->
            <table class="table table-striped table-bordered table-hover custom-table">
                ...
            </table>
            <!-- /Content list -->
            <!-- paging -->
            <ul class="pagination">
                ...
            </ul>
            <!-- /paging -->
        </div>
    </div>
    <!-- /Main content -->
    <!-- Delete confirmation pop-up -->
    <div class="modal fade confirm-modal">
        ...
    </div>
{{/block}}

{{block 'script'}}
    <script type="text/javascript">
        $('.delete').on('click', function () {
            // Get user id
            var id = $(this).attr('data-id');
            // Store the user id to be deleted in the hidden domain
            $('#deleteUserId').val(id);
        })
    </script>
{{/block}}

6, Related plug-ins

6.1 body-parser

Body parser is also an Express middleware, which is mainly used to parse the common request parameters submitted by post.
Its use is as follows:

//Introducing body parser module
const bodyParser = require('body-parser');
//Configure the body parser module, remember to write it in front of all Middleware
app.use(bodyParser.urlencoded({extended : false}));

In this way, you can access the data submitted by the form in req.body after the post submits the request.
However, the body parser can only parse ordinary form data. If there is file upload function to the project, the body parser cannot parse, and req.body will be an empty object. So we need to use the formatible third-party module.

6.2 formidable

The formatible third-party module is also used to parse forms. It supports get, post request parameters, and file upload functions.
First, install formatible, and enter:

npm i formidable

Then, in the routing module of the form data to be uploaded using the file:

//1. Introduce formatible
const formidable = require('formidable');
//2. Create a form resolution object
const form = new formidable.IncomingForm();
//3. Set file upload path
form.uploadDir = path.join(__dirname,'../','../','public','uploads');
//4. Keep the extension of the form upload file
form.keepExtensions = true;
//5. Parsing form
form.parse(req,async (err,fields,files) => {
	//Fields are used to access common form objects, such as fields.title
	//files is used to access the uploaded file. files.cover.path is the absolute path of the uploaded file
	...
});

Read file:

//1. Create file read object
var reader = new FileReader();
//2. Read file
reader.readAsDataURL(this.files[0]);
reader.onload = funtion(){
	//Save the file read results in the page for display
	preview.ssrc = reader.result;
}

6.3 bcypt

Bcypt is used to encrypt the password. The password in the database was originally stored in plaintext, which is extremely insecure. So we use bcypt to encrypt the password and store it in the database.
First, install bcypt, and on the command line, type:

npm i bcyptjs

In our project, the place to use password encryption is to add users. When we fill in the information of users to be added in the form and click Submit, the form data will be saved in req.body and passed to the corresponding routing processing module. In the routing module, we replace the encrypted password with req.body.password and save it in the database later.
See / route/user-edit-fn.js:

//1. Import bcypt
const bcypt = require('bcryptjs');
//2. Generate random string
const salt = bcrypt.genSalt(10);
//3. Use random string to encrypt password
const password = bcypt.hash(req.body.password,salt);
//Replace the encrypted password with req.body.password
req.body.password = password;

In the login page, there are also places where bcrpt is used: after we fill in the account password, we need to compare the password. At this time, call the compare method in bcypt to compare the entered password req.body.password with the password password in the database:

const bcrypt = rquire('bcrypt');
const password = req.body.password;
let isValid = bcypt.compare(password,user.password);

6.4 mongoose-sex-page

This plug-in is used to implement data paging. The collection constructor generated after importing this plug-in includes the following contents (json format):

{
	"page":1//Current page
	"size":2,//Number of data displayed per page
	"total": 8,//Total number of data
	"records":[
		//Here are the specific data found
		{
			"_id":"5c...",
			"title":"ceshi"
		}
	],
	"pages":4,//How many pages in total
	"display":[1,2,3,4]//Page number displayed by the client
}

The specific use of this project can be seen in / route/article.js routing module and / views/article.art.

//article.js
//1. Import mongoose sex page
const pagination = require('mongoose-sex-page');
//2. Import database
const {Article} = require('../../model/article');
module.exports = async (req,res) => {
    // Current page number passed from the client
    const page = req.query.page;
    //The objects locales can pass the added properties to the client
    req.app.locals.currentLink = 'article';
    // Page method specifies the current page 
    // The size method specifies the number of data bars displayed per page
    // The display method specifies the number of page numbers the client will display
    // exec method sends query request to database
    // Query all article data
    let count = await Article.countDocuments({});
    let articles = await pagination(Article).find().page(page).size(2).display(3).populate('author').exec();

    //  res.send(articles.records);
    //  return;
    res.render('admin/article.art',{
        articles:articles,
        count:count
    });
 }

At this time, the data articles transferred to the article.art page are in article.records.

6.5 Joi

Joi is the rule description and validator for JS objects.

In the project, I use it to verify whether the data of the database meets the requirements. The installation is also very simple, just type npm i Joi on the command line.
Then I create a verifier and export it by default, so that I can directly import the verifier to use when I need to verify the data. If the verification fails, an error will appear. We just need to determine whether we catch the error.

//model/user.js
const Joi = require('Joi');
...
const validateUser = user => {
    //Define validation rules
    const schema = {
        username:Joi.string().min(2).max(12).required().error(new Error('User name does not conform to validation rules')),
        email:Joi.string().email().required().error(new Error('Email format does not meet the requirements')),
        // regex (regular expression), range in A-Z, A-Z, 0-9, minimum 3, maximum 30
        password:Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).required().error(new Error('Password format does not meet the requirements')),
        // The valid method tells the client to pass only normal and admin, which are illegal
        role:Joi.string().valid('normal','admin').required().error(new Error('Illegal role value')),
        state:Joi.number().valid(0,1).required().error(new Error('Illegal status value'))
    };

    //Implementation verification
    return Joi.validate(user,schema);
}
...
module.exports = {
	User,
    validateUser
}

6.6 express-session

There is such a problem when logging in. We enter the account and password in login.art, and then automatically jump to the user list page according to the code. However, are we really logged in? In fact, even if we login successfully, the next time we visit the server, the server still doesn't recognize it. Therefore, there are some knowledge points about cookie s and session s.
Cookie is a storage space opened up by browser for pages, and session is a storage space opened up by server for users who visit. When a user accesses the server, the server needs to generate a sessionId to uniquely identify the user identity and store the sessionId in the client cookie. Then, the next time you visit the server, take this sessionId to access the server, and the server responds to the information that can only be obtained after the user logs in. Here, we use express session to implement session function.

//app.js
// Import express session
const session = require('express-session');
//Configure session
app.use(session({
    secret:'secret key',
    saveUnitialized:false,
    cookie:{
        maxAge:24*60*60*1000
    }
}));

7, A record of stepping on a pit

7.1 after installing bcrypt, an error occurred in require(bcypt).

When using bcrypt, there was an error [laughing and crying] because I wrote it like this. The installation is npm i bcrypt and then the import is require('bcrypt '). But in fact, you need to install bcryptjs under the window, and then require('bcryptjs') does not understand the principle for the time being. Maybe because the node.js version is different?

7.2 when the article is modified, the picture will not be displayed after clicking the modified file without uploading.

In the article editing page, the expected function is that if I don't upload the cover image again, the image will be the image uploaded when I first create the article data by default. But in formatible, no matter whether there are pictures or not, as long as I don't upload the pictures, he will default the files to be empty, and then he will generate a 0kb picture by himself, so that if I don't upload a picture again, I will click to submit the article cover picture to be empty.
So I added a judgment in the routing module article-modify.js: if I didn't upload the picture again, the files.cover.name would be empty, then I would directly take the cover in the database and update it.

let cover = "";
if(!files.cover.name){
	let article = await Article.findOne({_id:id});
    cover = article.cover;
}
else {
    cover = files.cover.path.split('public')[1];
}
// res.send(cover);
await Article.updateOne({_id:id},{
     title:fields.title,
     author:fields.author,
     publishDate:fields.publishDate,
     cover:cover,
     content:fields.content
});

Project address

Project address: Github->https://github.com/molifenge/selfblog
Running project:

nodemon app.js

Then, enter localhost:8080/admin/login in the browser to access it.

Published 4 original articles, won praise 0, visited 63
Private letter follow

Tags: Database MongoDB Mongoose Session

Posted on Sun, 23 Feb 2020 04:37:54 -0500 by PHPnewb_JavaAdept