egg.js实战CMS + webAPP笔记 (一)

608 阅读5分钟

Prerequires

  • familiar with HTML CSS js promise node.js npm
  • if you don't familiar with Vue site or egg site you can see the official docs then can come back to read this article later
  • the most important thing is: try it by yourself !

What you will lean in this article :

  1. build a frontend ui with vue-cli vue vuex vue-router
  2. build a backend server with egg.js and cors plugin template engine plugin (like egg-view-nunjucks) and sequelize(MySQL ORM) plugin
    • handle CURD with different categories like(users articles videos etc)
    • add egg router.resoure to simplify the router path
    • serve a static file in egg
    • deploy a backend server(todo later)
  3. handle cors
  4. send axios request in frontend
  5. build a backend cms with elementUI : admin can see update modify some resources in this system.
  6. build a pc/web page to show a demo website,can show articles videos user can also login logout etc

What is egg.js

Egg is born for building enterprise application and framework, we hope Egg will give birth to more application framework to help developers and developing teams reduce development and maintenance costs.

The fetaures of egg.js

  • Provide capability to customizd framework base on Egg
  • Highly extensible plugin mechanism
  • Built-in cluster
  • Based on Koa with high performance
  • Stable core framework with high test coverage
  • Progressive development

How to init?

npm init egg --type='simple'

if you run this command in a not empty dir . egg will tell you to select a new folder . then you can create a empty folder by yourself.

after install the dependiences. the project structor may like this below :

image.png

then cd server && npm run dev to start the server.after doing this . you can start a local server.

when you visit this url http://127.0.0.1:7001/ the egg can return a message for you, the message content is defined in the homeController like this :

//app/controller/home.js
"use strict";

const Controller = require("egg").Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    // change the return value 
    ctx.body = "hello alex from egg";
  }
}

serve a static file

you can also create a demo.html file in /app/public folder. then you can see the file content when you enter this url in the browser http://127.0.0.1:7001/public/demo.html

router

- MVC (View --- Model --- Controller)
- View : show data 
- Model: handle datas 
- Controller : handle user's input 

handle api service

add a new Controller then add the controller to router with a path name like/student. then visit this url http://127.0.0.1:7001/student in browser, will see the response is student the code is show in below:



//app/controller/student.js
"use strict";

 const Controller = require("egg").Controller;

 class StudentController extends Controller {
   async index() {
     const { ctx } = this;
     ctx.body = "student";
   }

 }

 module.exports = StudentController;


//app/router.js
"use strict";
 
 /**
  * @param {Egg.Application} app - egg application
  */
 module.exports = app => {
   const { router, controller } = app;
   router.get("/", controller.home.index);
   
   //add this line below
   router.get("/student", controller.student.index);
 };

we can konw that controller can also used in router.js to handle different path user visited in the frontend.

get query params and post datas in backend

  • get GET method query params use this.ctx.request.query
class StudentController extends Controller {
  async index() {
    const { ctx } = this;
    console.log(ctx.request.query);
    ctx.body = ctx.request.query;
  }
}


//in browser you can visit this url to test http://127.0.0.1:7001/student?age=18&sex=man
//the response is 


// http://127.0.0.1:7001/student?age=18&sex=man
{
  "age": "18",
  "sex": "man"
}
  • get GET method id use this.ctx.params
//add new path  in router.js
router.get("/student/:id", controller.student.getId);

//add a new method in controller student.js
async getId() {
    const { ctx } = this;
    let id = ctx.params.id;
    ctx.body = `当前请求路径的id是${id}`;
}



//http://127.0.0.1:7001/student/345
//response is 当前请求路径的id是345

  • get POST method use this.ctx.request.body

"use strict";

const Controller = require("egg").Controller;

let students = ["alex", "tom", "jony"];
class StudentController extends Controller {
  async index() {
    const { ctx } = this;
    const query = ctx.request.query;
    ctx.body = query;
  }

  async getId() {
    const { ctx } = this;
    let id = ctx.params.id;
    ctx.body = `当前请求路径的id是${id}`;
  }

// add this two methods 
  async createStudentPage() {
    const { ctx } = this;
    const formHtml = `
      <form action="/createStduent" method="post">
        <input type="text"  />
        <input type="submit" value="提交">
      </form>
    `;
    ctx.body = formHtml;
  }
  async addStudent() {
    let student = this.ctx.request.body;
    this.ctx.body = student;
  }
}

module.exports = StudentController;


//router.js

"use strict";

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get("/", controller.home.index);
  router.get("/student", controller.student.index);
  router.get("/student/:id", controller.student.getId);
  //add this two routes
  router.get("/createStduent", controller.student.createStudentPage);
  router.post("/createStduent", controller.student.addStudent);
};

after we sumit the form we will encounter a error : ForbiddenError in /createStduent invalid csrf token because egg has a csrf validation for the post mehthod . so we need to edit the config to omit the validation now.

Here we do the config temporarily in config/config.default.js for an example:

config.security = {
    csrf: {
      enable: false
    }
};

egg RESTful Style URL Definition to aviod define multi routes for one resource

before use router.resources:


//app/router.js
    //students router logic 
   router.get("/student", controller.student.index);
   router.get("/student/:id", controller.student.getId);
   router.get("/createStduent", controller.student.createStudentPage);
   router.post("/createStduent", controller.student.addStudent);

  

after use router.resources : we can handle this just by add one line!

//app/router.js
  // router.get("/student", controller.student.index);
  // router.get("/student/:id", controller.student.getId);
  // router.get("/createStduent", controller.student.createStudentPage);
  // router.post("/createStduent", controller.student.addStudent);

  router.resources("students", "/students", controller.student);

image.png

so we need to change to student controller to match the routers

student CURD demo :

  • http://127.0.0.1:7002/students get students list
  • http://127.0.0.1:7002/students/new get add students form page

"use strict";

const Controller = require("egg").Controller;

//define post datas here path : http://127.0.0.1:7002/students
let students = ["alex", "tom", "jony"];
class StudentController extends Controller {

//get method 
  async index() {
    const { ctx } = this;
    ctx.body = students;
  }

  //show add student post page  path : http://127.0.0.1:7002/students/new
  async new() {
    const { ctx } = this;
    const formHtml = `
      <form action="/students" method="post">
        <input type="text"  name="studentName" />
        <input type="submit" value="提交">
      </form>
    `;
    ctx.body = formHtml;
  }
  
    //add a new student post method
  // enter the url will send a get request
  // click reload will  repost
  async create() {
    let student = this.ctx.request.body;
    students.push(student.studentName);
    // remote this step
    // this.ctx.body = "add stducent success";
    //redirect to student list
    this.ctx.redirect("/students");
  }


  //  /students/:id    delete method
  async destory() {}
  //update put method   /students/:id
  async update() {}
}

module.exports = StudentController;

plugins

template engine npm i egg-view-nunjucks --save

npm i egg-view-nunjucks --save

then enable it 

// config/plugin.js
'use strict';

/** @type Egg.EggPlugin */
module.exports = {
  // had enabled by egg
  // static: {
  //   enable: true,
  // }
  nunjucks = {
    enable: true,
    package: 'egg-view-nunjucks'
  }
};

//and also config 
// config/config.default.js
exports.keys = <YOUR_SECURITY_COOKE_KEYS>;
// add view's configurations
exports.view = {
  defaultViewEngine: 'nunjucks',
  mapping: {
    '.tpl': 'nunjucks',
  },
};


then change the home controller

"use strict";

const Controller = require("egg").Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    // ctx.body = "hello alex from egg";
    //techs is the data the index.html will show 
    await ctx.render("index", { techs: ["js", "html", "css"] });
  }
}

module.exports = HomeController;


crate a new folder `view` in app  and a index.html and for loop the datas pass in the home controller 

//app/view/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <h1>techs :</h1>
    <ul>
      {% for tech in techs %}
      <li>{{ tech }}</li>
      {% endfor %}
    </ul>
  </body>
</html>


//more detail can reference egg hacknews demo https://sourcegraph.com/github.com/eggjs/examples@master/-/blob/hackernews/app/controller/news.js


the result is http://127.0.0.1:7001/ the router is / to homeController:

//app/router.js

"use strict";

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  // / => home controller
  router.get("/", controller.home.index);
  router.resources("students", "/students", controller.student);
};

//home.controller  app/controller/home.js

"use strict";

const Controller = require("egg").Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    // ctx.body = "hello alex from egg";
    await ctx.render("index", { techs: ["js", "html", "css"] });
  }
}

module.exports = HomeController;



use template result in frontend : image.png

cors

when front end (8080 port) send request to backend (7001) the browser will have a error

  • vue cli create a new project call clint cd clinet npm run serve then change the app.vue the project folder looks like this :
/client (vue.js forentend folder) in 8080 port
/server (egg.js backend server floder) in 7001 port
  • add axios in client foldernpm i axios -S

// client/src/app.vue

<template>
  <h1>{{message}}</h1>
</template>

<script>
import axios from "axios";
export default {
  name: "App",
  data() {
    return {
      message: "hello vue"
    };
  },
  created() {
    axios.get("http://127.0.0.1:7001/test").then(
      res => {
        console.log(res);
        this.message = res.data;
      },
      err => {
        console.log(err);
      }
    );
  }
};
</script>


when we visit this url in frontend we will encounter this error Access to XMLHttpRequest at 'http://127.0.0.1:7001/test' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

so we need to config the cors in our backend

how to enable cors in egg.js ?

cd server/ && npm i -S egg-cors 


//plugin.js

  cors: {
    enable: true,
    package: "egg-cors"
  }
  
//config.default.js

  config.cors = {
    //allow all the origin
    origin: "*",
    allowMethods: "GET,HEAD,PUT,POST,DELETE,PATCH"
  };
  
 
 

after configing this we vist the frontend again will see the success result :

image.png

build a CURD demo with egg and vue

frontend vue app.vue

<template>
  <div>
    <h1>{{message}}</h1>
    <ul>
      <li v-for="(stu, index) of  students" :key="index">{{stu}}</li>
    </ul>
  </div>
</template>

<script>
import axios from "axios";
export default {
  name: "App",
  data() {
    return {
      message: "hello vue",
      students: []
    };
  },
  created() {
    //test cors
    // axios.get("http://127.0.0.1:7001/test").then(
    //   res => {
    //     console.log(res);
    //     this.message = res.data;
    //   },
    //   err => {
    //     console.log(err);
    //   }
    // );

    //get students
    axios.get("http://127.0.0.1:7001/students").then(
      res => {
        console.log(res);
        //why use res.data because  we can check the result data  in  chrome devtools 
        this.students = res.data;
      },
      err => {
        console.log(err);
      }
    );
  },
  methods: {}
};
</script>


backend student api js

the result islocalhost:8080 : the frontend show the backend data successfully

image.png

  • add post method in frontend

//client/src/app.vue

<template>
  <div>
    <h1>{{message}}</h1>
    <form @submit.prevent="postStudent">
      <input type="text" v-model="nowInputedSutdentName" />
      <button>add a student name</button>
    </form>
    <ul>
      <li v-for="(stu, index) of  students" :key="index">{{stu}}</li>
    </ul>
  </div>
</template>

<script>
import axios from "axios";
export default {
  name: "App",
  data() {
    return {
      message: "hello vue",
      students: [],
      nowInputedSutdentName: ""
    };
  },
  created() {
    //test cors
    // axios.get("http://127.0.0.1:7001/test").then(
    //   res => {
    //     console.log(res);
    //     this.message = res.data;
    //   },
    //   err => {
    //     console.log(err);
    //   }
    // );

    //get students
    this.getStudent();
  },
  methods: {
    getStudent() {
      axios.get("http://127.0.0.1:7001/students").then(
        res => {
          console.log(res);
          this.students = res.data;
        },
        err => {
          console.log(err);
        }
      );
    },
    postStudent() {
      axios
        .post("http://127.0.0.1:7001/students", {
          studentName: this.nowInputedSutdentName
        })
        .then(() => {
          //reset  this.nowInputedSutdentName
          this.nowInputedSutdentName = "";
          this.getStudent();
        });
    }
  }
};
</script>



//the backend sutdent api is 

"use strict";

const Controller = require("egg").Controller;

//define post datas here
let students = ["alex", "tom", "jony"];
class StudentController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = students;
  }

  //add a new student post method
  // enter the url will send a get request
  // click reload will  repost
  // post method to add new student
  async create() {
    // {studentName : "xxx"}
    let student = this.ctx.request.body;
    students.push(student.studentName);
    // remote this step
    // this.ctx.body = "add stducent success";
    //redirect to student list
    this.ctx.redirect("/students");
  }

  //  /students/:id    delete method
  async destory() {
    let { ctx } = this;
    let id = ctx.params.id;
    students.splice(id, 1);
    ctx.body = "delete success";
  }

  //show add student post page
  async new() {
    const { ctx } = this;
    const formHtml = `
      <form action="/students" method="post">
        <input type="text"  name="studentName" />
        <input type="submit" value="提交">
      </form>
    `;
    ctx.body = formHtml;
  }

  //update put method   /students/:id
  async update() {}

  // async getId() {
  //   const { ctx } = this;
  //   let id = ctx.params.id;
  //   ctx.body = `当前请求路径的id是${id}`;
  // }

  // async createStudentPage() {
  //   const { ctx } = this;
  //   const formHtml = `
  //     <form action="/createStduent" method="post">
  //       <input type="text"  name="studentName" />
  //       <input type="submit" value="提交">
  //     </form>
  //   `;
  //   ctx.body = formHtml;
  // }
  // async addStudent() {
  //   let student = this.ctx.request.body;
  //   console.log(student); //{ studentName: 'wewwewe' }
  //   students.push(student.studentName);
  //   // this.ctx.body = "add stducent success";
  //   //redirect to student list
  //   this.ctx.redirect("/student");
  // }
}

module.exports = StudentController;


the result is after user post a new student name. the list will get the students againg to show the newest datas :

image.png

next article we will talk about how to use middleware data persistence service deploy etc

middlewares

data persistence

- use `sequelize`(Object Relational Mapping ORM framework)  to interact with mysql. you don't need to write pure sql statement by yourself .

service

deploy

最后

  • source code fe backend-egg 觉得有帮助,麻烦给个star!
  • 前端关宇
  • 微信号: wuguanyu1103 加我备注“掘金” 拉你进每日前端早起学习群,和小伙伴一起玩转前端。
  • 感谢阅读