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); } }
访问该接口:
当然,我们要知道,生产实战中,是不可能把这种报错直接返回给客户端的,因为这样很不友好,而且也很不安全!