RealWorld官方Github地址:https://github.com/gothinkster/realworld
接口需求文档文档地址:https://realworld-docs.netlify.app/docs/specs/backend-specs/endpoints/
一、快速手动搭建基本项目结构
1、创建项目,并安装express:
realworld-api-express cd realworld-api-express npm init -y npm i express@4.18.2 code .
2、创建项目入口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}`);
});
这时候好习惯,是要启动测试一下!
nodemon app.js
3、设计好express的项目结构(规范):
. |-- config # 配置文件 |-- config.default.js |-- controller # 用于解析用户的输入,处理后返回相应的结果 |-- model # 数据持久层 |-- middleware # 用于编写中间件 |-- router # 用于哦欸之URL路由规则 |-- util # 工具模块 |-- app.js # 用于自定义启动时的初始化工作
其中默认配置文件 config.default.js 内容如下:
/**
* 默认配置
*/
module.exports = {
}
4、配置常用的中间件:
-
解析请求体中间件:
app.use(express.json()); app.use(express.urlencoded());
-
日志输出中间件:
// morgan属于第三方中间件,需要安装
npm i morgan
// 代码中使用:
const morgan = require('morgan');
app.use(morgan('dev'));
-
为客户端提供跨域请求支持的中间件:
// cors也属于第三方中间件,需要安装
npm i cors
// 在代码中使用
const cors = require('cors');
app.use(cors());
5、最终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.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`);
});
二、路由设计
1、入口文件 app.js 中挂载主路由:
const express = require("express");
const morgan = require('morgan');
const cors = require('cors');
const router = require("./router");
const app = express();
app.use(morgan('dev'));
app.use(cors());
app.use(express.json());
app.use(express.urlencoded());
const PORT = process.env.PORT || 3000;
// 挂载路由router
app.use('/api', router)
app.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`);
});
2、主路由 ./router/index.js 中挂载每一个分路由:
// 主路由
const express = require('express');
const router = express.Router();
// 用户相关路由
router.use(require('./user'))
// 用户资料相关路由
router.use('/profiles', require('./profile'))
// 文章相关路由
router.use("/articles", require("./article"));
// 标签相关路由
router.use(require("./tag"));
module.exports = router;
3、对应的4个分路由文件:
-
./router/user.js :
// 用户相关路由
const express = require('express');
const router = express.Router();
// 用户登录
router.post('/users/login', async (req, res, next) => {
try {
// 处理请求
res.send('post /users/login');
} catch (error) {
next(error);
}
})
// 用户注册
router.post('/users', async (req, res, next) => {
try {
// 处理请求
res.send('post /users');
} catch (error) {
next(error);
}
})
// 获取当前登录用户
router.get('/user', async (req, res, next) => {
try {
// 处理请求
res.send('get /user');
} catch (error) {
next(error);
}
})
// 更新当前登陆用户资料
router.put('/user', async (req, res, next) => {
try {
// 处理请求
res.send('put /user');
} catch (error) {
next(error);
}
})
module.exports = router;
-
./router/profile.js :
// Profile相关路由
const express = require('express');
const router = express.Router();
// 获取指定用户资料
router.post('/:username', async (req, res, next) => {
try {
// 处理请求
res.send('post /profiles/:username');
} catch (error) {
next(error);
}
})
// 关注用户
router.post('/:username/follow', async (req, res, next) => {
try {
// 处理请求
res.send('post /profiles/:username/follow');
} catch (error) {
next(error);
}
})
// 取消关注用户
router.delete('/:username/follow', async (req, res, next) => {
try {
// 处理请求
res.send('delete /profiles/:username/follow');
} catch (error) {
next(error);
}
})
module.exports = router;
-
./router/article.js :
// 文章相关路由
const express = require("express");
const router = express.Router();
// List Articles
router.get("/", async (req, res, next) => {
try {
// 处理请求
res.send("get /");
} catch (err) {
next(err);
}
});
// Feed Articles
router.get("/feed", async (req, res, next) => {
try {
// 处理请求
res.send("get /articles/feed");
} catch (err) {
next(err);
}
});
// Get Article
router.get("/:slug", async (req, res, next) => {
try {
// 处理请求
res.send("get /articles/:slug");
} catch (err) {
next(err);
}
});
// Create Article
router.post("/", async (req, res, next) => {
try {
// 处理请求
res.send("post /articles");
} catch (err) {
next(err);
}
});
// Update Article
router.put("/:slug", async (req, res, next) => {
try {
// 处理请求
res.send("put /articles/:slug");
} catch (err) {
next(err);
}
});
// Delete Article
router.delete("/:slug", async (req, res, next) => {
try {
// 处理请求
res.send("delete /articles/:slug");
} catch (err) {
next(err);
}
});
// Add Comments to an Article
router.post("/:slug/comments", async (req, res, next) => {
try {
// 处理请求
res.send("post /articles/:slug/comments");
} catch (err) {
next(err);
}
});
// Get Comments from an Article
router.get("/:slug/comments", async (req, res, next) => {
try {
// 处理请求
res.send("get /articles/:slug/comments");
} catch (err) {
next(err);
}
});
// Delete Comment
router.delete("/:slug/comments/:id", async (req, res, next) => {
try {
// 处理请求
res.send("delete /articles/:slug/comments/:id");
} catch (err) {
next(err);
}
});
// Favorite Article
router.post("/:slug/favorite", async (req, res, next) => {
try {
// 处理请求
res.send("post /articles/:slug/favorite");
} catch (err) {
next(err);
}
});
// Unfavorite Article
router.delete("/:slug/favorite", async (req, res, next) => {
try {
// 处理请求
res.send("delete /articles/:slug/favorite");
} catch (err) {
next(err);
}
});
module.exports = router;
-
./router/tag.js :
// 标签相关路由
const express = require("express");
const router = express.Router();
// Get Tags
router.get("/tags", async (req, res, next) => {
try {
// 处理请求
res.send("get /tags");
} catch (err) {
next(err);
}
});
module.exports = router;
三、提取控制器模块
1、以user模块为例, ./controller/user.js 内容如下:
// 用户登录
exports.login = async (req, res, next) => {
try {
// 处理请求
res.send('login');
} catch (error) {
next(error);
}
}
// 用户注册
exports.register = async (req, res, next) => {
try {
// 处理请求
res.send('register');
} catch (error) {
next(error);
}
}
// 获取当前登录用户
exports.getCurrentUser = async (req, res, next) => {
try {
// 处理请求
res.send('getCurrentUser');
} catch (error) {
next(error);
}
}
// 更新当前登陆用户资料
exports.updateCurrentUser = async (req, res, next) => {
try {
// 处理请求
res.send('updateCurrentUser');
} catch (error) {
next(error);
}
}
2、那么在 ./router/user.js 中就可以直接用用userCtrl中的方法即可:
// 用户相关路由
const express = require('express');
const userCtrl = require('../controller/user')
const router = express.Router();
// 用户登录
router.post('/users/login', userCtrl.login);
// 用户注册
router.post('/users', userCtrl.register);
// 获取当前登录用户
router.get('/user', userCtrl.getCurrentUser);
// 更新当前登陆用户资料
router.put('/user', userCtrl.updateCurrentUser);
module.exports = router;
3、挂载异常统一处理中间件:
在 ./middleware 目录下,创建自定义的中间件 error-handler.js:
const util = require("util");
module.exports = () => {
return (err, req, res, next) => {
res.status(500).json({
error: util.format(err),
});
};
};
app.js中引入并挂载:
const errorHandler = require('./middleware/error-handler')
// 在app.js的最后,挂载统一异常处理中间件
app.use(errorHandler());
4、测试统一异常处理:
随便找个接口的ctrl中,添加一段肯定报错的代码:
// 获取当前登录用户
exports.getCurrentUser = async (req, res, next) => {
try {
// 处理请求
JSON.parse('abc');
res.send('getCurrentUser');
} catch (error) {
next(error);
}
}
访问该接口:

当然,我们要知道,生产实战中,是不可能把这种报错直接返回给客户端的,因为这样很不友好,而且也很不安全!

