Building a RESTful Backend API with Node.js and Express.js
01 Create Server with Express.js
npm install express --save
- 根目录下增加dev-server, prod-server
- 创建dev-server/index.js, 参考express的helloword代码
const express = require("express");
const app = express();
const port = 3000;
app.get("/", (req, res) => res.send("Hello World!"));
app.listen(port, () => console.log(`EXAMPLE app listening on port ${port}!`));
npm install nodemon -g
运行:
nodemon dev-server/index.js
- 启动测试服务: http://localhost:3000
nodemon 运行过程中输入rs手动重启服务
参考: https://babeljs.io/docs/en/usage
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill
- 新建dev-server/.babelrc
{
"presets": ["@babel/preset-env"]
}
package.json下script增加:
"dev": "babel dev-server --out-dir prod-server --watch",
修改dev-server/index.js
// const express = require("express");
import express from "express";
运行
nodemon prod-server
测试开发服务是否正常运行。
npm install concurrently --save-dev
- package.json 下的script参考:
"scripts": {
"serve": "vue-cli-service serve",
"vuebuild": "vue-cli-service build",
"dev": "babel dev-server --out-dir prod-server --watch",
"build": "babel dev-server --out-dir prod-server && vue-cli-service build",
"concurrently": "concurrently \"babel dev-server --out-dir prod-server --watch\" \"nodemon prod-server/index.js\" \"npm run serve\" ",
"lint": "vue-cli-service lint",
"lintfix": "eslint ./dev-server --fix && vue-cli-service lint"
},
02 Learn to Use Express.js Router
- 创建dev-server/api/task/tasks-routes.js
import express from "express";
const router = express.Router();
router.post("/task", (req, res) => {
res.send("post.task - create a task");
});
router.get("/task", (req, res) => {
res.send("get.task - get all tasks");
});
router.get("/task/:id", (req, res) => {
res.send("get.task/:id - get task by id");
});
router.put("/task/", (req, res) => {
res.send("put.task - update a task");
});
router.delete("/task/", (req, res) => {
res.send("delete.task - update a task");
});
export default router;
- 创建dev-server/routes.js
import taskRoutes from "./api/task/tasks-routes";
export function registerRoutes(app) {
app.use("/api", taskRoutes);
}
- 修改dev-server/index.js
// const express = require("express");
import express from "express";
const app = express();
const port = 3000;
//===========注册路由==========
import { registerRoutes } from "./routes";
registerRoutes(app);
app.get("/", (req, res) => res.send("Hello World!"));
app.listen(port, () => console.log(`EXAMPLE app listening on port ${port}!`));
- 使用postman测试
- 增加dev-server/api/auth/auth-routes.js
import express from "express";
const router = express.Router();
const apiName = "auth";
router.post(`/${apiName}`, (req, res) => {
res.send(`post.${apiName} - login`);
});
export default router;
- 增加dev-server/api/register/register-routes.js
import express from "express";
const router = express.Router();
const apiName = "register";
router.post(`/${apiName}`, (req, res) => {
res.send(`post.${apiName} - register a user`);
});
export default router;
- 增加dev-server/api/user/user-routes.js
import express from "express";
const router = express.Router();
const apiName = "user";
router.get(`/${apiName}`, (req, res) => {
res.send(`get.${apiName} - get all user`);
});
export default router;
- 修改dev-server/routes.js
import taskRoutes from "./api/task/tasks-routes";
import regRoutes from "./api/register/register-routes";
import userRoutes from "./api/user/user-routes";
import authRoutes from "./api/auth/auth-routes";
export function registerRoutes(app) {
app.use("/api", taskRoutes);
app.use("/api", regRoutes);
app.use("/api", userRoutes);
app.use("/api", authRoutes);
}
03 Use Express.js Middleware and CORS Configuration
npm install cors morgan --save-dev
npm install body-parser --save
- 新建dev-server/config/env.js
import express from "express";
import morgan from "morgan";
import cors from "cors";
import bodyParser from "body-parser";
export function setEnvironment(app) {
if (process.env.NODE_ENV !== "production") {
setDevEnv(app);
} else {
setProdEnv(app);
}
}
function setDevEnv(app) {
// console.log("setting development environment");
process.env.NODE_ENV = "development";
app.use(bodyParser.json());
app.use(morgan("dev"));
app.use(cors());
}
function setProdEnv(app) {
app.use(bodyParser.json());
app.use(express.static(__dirname + "/../dist"));
console.log("setting production environment");
}
- 修改dev-server/index,引入setEnvironment middleware
// const express = require('express')
import express from "express";
const port = 3000;
const app = express();
import { registerRoutes } from "./routes";
import { setEnvironment } from "./config/env";
setEnvironment(app);
registerRoutes(app);
// app.get('/', (req, res) => res.send('Hello World!'))
app.get("/", (req, res) => {
if (process.env.NODE_ENV !== "production") {
return res.send("Running server in devlopment mode.");
} else {
return res.sendFile("index.html"), { root: __dirname + "/../dist" };
}
});
app.listen(port, () => {
const msg = `MEVN app listening on port ${port} and run in ${process.env.NODE_ENV} mode!`;
console.log(msg);
});
- package.json的script增加
"build": "set NODE_ENV=production && babel dev-server --out-dir prod-server && vue-cli-service build",
"concurrently": "concurrently \"set NODE_ENV=development\" \"babel dev-server --out-dir prod-server --watch\" \"nodemon prod-server/index.js\" \"npm run serve\" ",
注意linux环境应该是export NODE_ENV=production
04 RESTful Endpoints with HTTP Controllers
- 新建dev-server/api/user/user-controller.js
export function index(req, res) {
return res.json({ message: "Hello World" });
}
- 更改dev-server/api/user/user-routes.js
import express from "express";
const router = express.Router();
const apiName = "user";
import * as controller from "./user-controller";
/* router.get(`/${apiName}`, (req, res) => {
res.send(`get.${apiName} - get all user`);
}); */
router.get(`/${apiName}`, controller.index);
export default router;
- 修改Home.vue增加beforeCreate
<template>
<div id="custom-home">
<Main />
</div>
</template>
<script>
import Main from "@/components/Main.vue";
export default {
name: "home",
components: {
Main,
},
beforeCreate: function () {
fetch(this.$store.state.apiUrl + "/user", {
method: "GET",
})
.then((res) => res.json())
.then((res) => console.log(res));
},
};
</script>
- 浏览器端,测试验证
- 增加utilities/string-util.js
//babel编译抛错
/*
export class StringUtil {
static isEmpty(value) {
return !value || !value.trim();
}
static capitalize(word) {
return word.charAt(0).toUpperCase();
}
}
*/
export const isEmpty = (value) => {
return !value || !value.trim();
};
export const capitalize = (word) => {
return word.charAt(0).toUpperCase();
};
- 增加dev-server/register/register-controller.js
//import { StringUtil } from "../../utilities/string-util";
import * as StringUtil from "../../utilities/string-util";
export function index(req, res) {
const validation = validateIndex(req.body);
if (!validation.isValid) {
return res.json({ message: validation.message });
}
const user = {
username: req.body.username.toLowercase(),
password: req.body.password,
};
console.log(user);
return res.json();
}
function validateIndex(body) {
let errors = "";
if (StringUtil.isEmpty(body.username)) {
errors += "Username is required. ";
}
if (StringUtil.isEmpty(body.password)) {
errors += "Password is required. ";
}
return {
isValid: StringUtil.isEmpty(errors),
message: errors,
};
}
- 修改dev-server/register/register-routes.js, 引入controller
import express from 'express';
const router = express.Router();
import * as controller from './register-controller';
router.post('/register', controller.index);
export default router;
- 新建dev-server/api/task/tasks-controller.js
export function index(req, res) {
// FIND ALL TASKS
return res.json();
}
export function create(req, res) {
return res.json();
}
export function update(req, res) {
// UPDATE TASKS
return res.json();
}
export function remove(req, res) {
// DELETE A TASK
return res.json();
}
export function show(req, res) {
// GET TASK BY ID
return res.json();
}
- 更改dev-server/api/task/tasks-routes.js, 引入controller
import express from "express";
const router = express.Router();
import * as controller from "./tasks-controller";
router.post("/task", controller.create);
router.get("/task", controller.index);
router.get("/task/:id", controller.show);
router.put("/task", controller.update);
router.delete("/task", controller.remove);
export default router;
- 增加dev-server/api/auth/auth-controller.js
//import { StringUtil } from "../../utilities/string-util";
import * as StringUtil from "../../utilities/string-util";
export function index(req, res) {
const validation = validateIndex(req.body);
if (!validation.isValid) {
return res.json({ message: validation.message });
}
return res.json();
}
function validateIndex(body) {
let errors = "";
if (StringUtil.isEmpty(body.username)) {
errors += "Username is required. ";
}
if (StringUtil.isEmpty(body.password)) {
errors += "Password is required. ";
}
return {
isValid: StringUtil.isEmpty(errors),
message: errors,
};
}
- 修改dev-server/api/auth/auth-routes.js, 引入controller
import express from "express";
const router = express.Router();
const apiName = "auth";
import * as controller from "./auth-controller";
/* router.post(`/${apiName}`, (req, res) => {
res.send(`post.${apiName} - login`);
}); */
router.post(`/${apiName}`, controller.index);
export default router;
05 Check HTTP Status Codes
http的状态码的意义,参考:https://httpstatuses.com/
- 修改dev-server\api\auth\auth-controller.js
import * as StringUtil from "../../utilities/string-util";
export function index(req, res) {
const validation = validateIndex(req.body);
if (!validation.isValid) {
return res.status(400).json({ message: validation.message });
}
return res.status(201).json();
}
function validateIndex(body) {
let errors = "";
if (StringUtil.isEmpty(body.username)) {
errors += "Username is required. ";
}
if (StringUtil.isEmpty(body.password)) {
errors += "Password is required. ";
}
return {
isValid: StringUtil.isEmpty(errors),
message: errors,
};
}
- 修改dev-server\api\register\register-controller.js
// import { StringUtil } from "../../utilities/string-util";
import * as StringUtil from "../../utilities/string-util";
export function index(req, res) {
const validation = validateIndex(req.body);
if (!validation.isValid) {
return res.status(400).json({ message: validation.message });
}
const user = {
username: req.body.username.toLowercase(),
password: req.body.password,
};
console.log(user);
return res.status(201).json();
}
function validateIndex(body) {
let errors = "";
if (StringUtil.isEmpty(body.username)) {
errors += "Username is required. ";
}
if (StringUtil.isEmpty(body.password)) {
errors += "Password is required. ";
}
return {
isValid: StringUtil.isEmpty(errors),
message: errors,
};
}
- 修改dev-server\api\task\tasks-controller.js
export function index(req, res) {
// FIND ALL TASKS
return res.status(200).json();
}
export function create(req, res) {
return res.status(201).json();
}
export function update(req, res) {
// UPDATE TASKS
return res.status(204).json();
}
export function remove(req, res) {
// DELETE A TASK
return res.status(204).json();
}
export function show(req, res) {
// GET TASK BY ID
return res.status(200).json();
}
- 修改dev-server\api\user\user-controller.js
export function index(req, res) {
return res.status(200).json({ message: "Hello World" });
}
- 注释Home.vue中的beforeCreate方法中的一行,测试
beforeCreate: function () {
fetch(this.$store.state.apiUrl + "/user", {
method: "GET",
})
// .then((res) => res.json())
.then((res) => console.log(res));
},