优化应用结构
对于较小的应用,结构并不重要。可一旦应用开始增大,就必须建立某种结构,并将应用的不同职责分离到单独的模块中,使开发应用更加容易。
优化后的目录结构:
├── index.js
├── app.js
├── build
│ └── ...
├── controllers
│ └── blogs.js
├── models
│ └── blog.js
├── package-lock.json
├── package.json
├── utils
│ ├── config.js
│ ├── logger.js
│ └── middleware.js
index.js
:用于启动应用。app.js
:实际的应用。controllers/blogs.js
:路由处理程序。models/blog.js
:为 blogs 定义 Mongoose schema。utils/logger.js
:用于控制台的打印输出,告诉应用的运行状态。utils/config.js
:环境变量的处理,应用的其他部分可以通过导入配置模块来访问环境变量。utils/middleware.js
:自定义中间件模块。
以 bloglist 应用为例,将该应用重构。
初始 index.js:
const http = require('http')
const express = require('express')
const app = express()
const cors = require('cors')
const mongoose = require('mongoose')
const blogSchema = new mongoose.Schema({
title: String,
author: String,
url: String,
likes: Number
})
const Blog = mongoose.model('Blog', blogSchema)
const mongoUrl = 'mongodb://localhost/bloglist'
mongoose.connect(mongoUrl)
app.use(cors())
app.use(express.json())
app.get('/api/blogs', (request, response) => {
Blog
.find({})
.then(blogs => {
response.json(blogs)
})
})
app.post('/api/blogs', (request, response) => {
const blog = new Blog(request.body)
blog
.save()
.then(result => {
response.status(201).json(result)
})
})
const PORT = 3003
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})
models/blog.js
将建立到数据库连接的责任交给 app.js 模块,models 目录下的 blog.js 只为 blogs 定义 Mongoose schema。
const mongoose = require("mongoose");
const blogSchema = new mongoose.Schema({
title: String,
author: String,
url: String,
likes: Number,
});
blogSchema.set("toJSON", {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString();
delete returnedObject._id;
delete returnedObject.__v;
},
});
module.exports = mongoose.model("Blog", blogSchema);
controller/blogs.js
路由实际上是一个中间件,可用于在某个位置定义“相关路由” ,通常放置在单独的模块中。
路由的事件处理程序通常称为controllers。所有与 blog 相关的路由现在都在controllers 目录下的blogs.js 模块中定义。
controllers/blogs.js:
const blogsRouter = require("express").Router();
const Blog = require("../models/blog");
blogsRouter.get("/", (request, response) => {
Blog.find({})
.then((blogs) => {
response.json(blogs);
})
.catch((error) => next(error));
});
blogsRouter.post("/", (request, response) => {
const blog = new Blog(request.body);
blog
.save()
.then((result) => {
response.status(201).json(result);
})
.catch((error) => next(error));
});
module.exports = blogsRouter;
在文件的开始创建了一个新的 Router 对象。该模块将路由导出,所有消费者可用。
blogsRouter 对象必须只定义路由的相对部分,即空路径 /
或仅仅定义参数 /:id
。
app.js 对路由对象使用use方法,按如下方式使用:
const blogsRouter = require('./controllers/blogs')
app.use('/api/blogs', blogsRouter)
utils
config.js
专门环境变量的处理,应用的其他部分可以通过导入配置模块来访问环境变量。
require('dotenv').config()
const PORT = process.env.PORT
const MONGODB_URI = process.env.MONGODB_URI
module.exports = {
MONGODB_URI,
PORT
}
logger.js
info 用于打印正常的日志消息,error 用于所有错误消息。
const info = (...params) => {
console.log(...params)
}
const error = (...params) => {
console.error(...params)
}
module.exports = {
info, error
}
middleware.js
自定义中间件。
const logger = require('./logger')
const requestLogger = (request, response, next) => {
logger.info('Method:', request.method)
logger.info('Path: ', request.path)
logger.info('Body: ', request.body)
logger.info('---')
next()
}
const unknownEndpoint = (request, response) => {
response.status(404).send({ error: 'unknown endpoint' })
}
const errorHandler = (error, request, response, next) => {
logger.error(error.message)
if (error.name === 'CastError') {
return response.status(400).send({ error: 'malformatted id' })
} else if (error.name === 'ValidationError') {
return response.status(400).json({ error: error.message })
}
next(error)
}
module.exports = {
requestLogger,
unknownEndpoint,
errorHandler
}
app.js
进行重构后的 app.js:
const config = require("./utils/config");
const express = require("express");
const app = express();
const cors = require("cors");
const blogsRouter = require("./controllers/blogs");
const middleware = require("./utils/middleware");
const logger = require("./utils/logger");
const mongoose = require("mongoose");
logger.info("connecting to", config.MONGODB_URI);
mongoose
.connect(config.MONGODB_URI)
.then(() => {
logger.info("connected to MongoDB");
})
.catch((error) => {
logger.error("error connecting to MongoDB:", error.message);
});
app.use(cors());
app.use(express.static("build"));
app.use(express.json());
app.use(middleware.requestLogger);
app.use("/api/blogs", blogsRouter);
app.use(middleware.unknownEndpoint);
app.use(middleware.errorHandler);
module.exports = app;
index.js
index.js 文件只从 app.js 文件导入实际的应用,然后启动应用。
index.js 简化后如下:
const app = require('./app')
const http = require('http')
const config = require('./utils/config')
const logger = require('./utils/logger')
const server = http.createServer(app)
server.listen(config.PORT, () => {
logger.info(`Server running on port ${config.PORT}`)
})