通过Express框架实现realworld项目的后端API服务

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);
    }
}

访问该接口:

1686907961855244.png

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

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐