如何从Express.js中的应用程序级中间件中访问参数列表?

huangapple go评论67阅读模式
英文:

How to access parameter list from application-level middlewares in express.js?

问题

如何访问每个传入请求中的参数?

我有一个专门用于检查是否存在具有recordId的记录的中间件,该记录存在于我的MongoDB数据库中。
现在,我希望每个单独的请求都通过这个中间件,以遵循DRY的灵感。因此,我创建了一个应用级别的中间件:
app.use(checkRecordsExistence)

中间件checkRecordsExistence 需要访问所有参数列表,以检查相关记录的存在并将它们存储在req对象中以供进一步使用。

然而,req.params 总是返回{}。前进的方法是什么?我不想将其添加到路由级别的中间件中,因为这不够干净,我崇尚DRY。

这是我的详细实现。

const checkRecordsExistence = asyncHandler(async (req, res, next) => {
    const { params } = req;
    for (const key in params) {
        if (Object.hasOwnProperty.call(params, key)) {
            // 只检查与 Object Id 对应的参数
            if (key.endsWith('Id')) {
                // 从参数名称中删除 "Id" 后缀
                const modelNameLower = key.slice(0, -2);
                const modelName = modelNameLower.charAt(0).toUpperCase() + modelNameLower.slice(1);

                const Model = mongoose.model(modelName);
                const recordId = params[key];
                const record = await Model.findById(recordId);
                if (!record) {
                    return next(new ErrorResponse(
                        new Error(message.notFound(modelName)),
                        404
                    ));
                }
                // 将找到的记录附加到请求对象以供进一步使用
                // <==> req._class = await Class.findById(classId)
                req[`_${modelNameLower}`] = record;
                console.log(typeof(record));
            }
        }
    }
    next();
});

通过将中间件放在每个路由级别的路由中,我可以很好地运行它。但这并不干净。

英文:

How to access the params in every incoming requests?

I got a middleware dedicated to checking if the record with recordId existed in my MongoDB database.
Now I wish every single request to go through this middleware with the inspiration from DRY. So I created an application-level middleware:
app.use(checkRecordsExistence)

The middleware checkRecordsExistence needs to access all param list in order to check the existence of relevant records and store them in req object for further use.

However req.params always returns {}. What is the way forward? I don't want to add it in the router-level middlewares, because it is not clean, I worship DRY.

Here is my detailed implementation.

const checkRecordsExistence = asyncHandler(async (req, res, next) =&gt; {
    const { params } = req;
    for (const key in params) {
        if (Object.hasOwnProperty.call(params, key)) {
            // Only check params corresponding to Object Id
            if (key.endsWith(&#39;Id&#39;)) {
                // Remove the &quot;Id&quot; suffix from the param name
                const modelNameLower = key.slice(0, -2);
                const modelName = modelNameLower.charAt(0).toUpperCase() + modelNameLower.slice(1);

                const Model = mongoose.model(modelName);
                const recordId = params[key];
                const record = await Model.findById(recordId);
                if (!record) {
                    return next(new ErrorResponse(
                        new Error(message.notFound(modelName)),
                        404
                    ));
                }
                // Attach the found record to the request object for further use
                // &lt;=&gt; req._class = await Class.findById(classId)
                req[`_${modelNameLower}`] = record;
                console.log(typeof(record));
            }
        }
    }
    next();
});

I can get it going well by putting the middleware in every single router-level route. But it is not clean.

答案1

得分: 1

express 根据提供的路径分步构建req.params
当您在根应用程序上使用中间件(app.use(...))时,中间件不知道下游设置的:param

示例:

const express = require('express')
const app = express()
const port = 3000

// 在所有路由之前使用中间件
app.use(printParamsMiddleware)

// 在特定路由之前使用中间件
app.get('/users/:userId', printParamsMiddleware, (req, res) => {
  res.send('在 GET /user/:userId 中')
})

app.listen(port, () => {
  console.log(`示例应用正在监听端口 ${port}`)
})

function printParamsMiddleware(req, res, next) {
    console.log('打印参数', req.params)
    next()
}
curl localhost:3000/users/1234

输出

示例应用正在监听端口 3000
打印参数 {}
打印参数 { userId: '1234' }

从示例中可以看到,在通过声明的路径路由后,req.params被设置。


解决方案建议

我建议您创建一个中间件工厂,生成一个特定资源的自定义中间件。

// buildCheckRecordsExistence.middleware.js
function buildCheckRecordsExistence(modelName, paramKey) {
    return async function printParamsMiddleware(req, res, next) {
        const Model = mongoose.model(modelName);
        const recordId = req.params[paramKey];

        const record = await Model.findById(recordId);
        if (!record) {
            return next(new ErrorResponse(
                new Error(message.notFound(modelName)),
                404
            ));
        }
        req[paramKey] = record;
        next();
    }
}
// users.controller.js

// 为用户创建一个特定的检查记录中间件
const usersCheckRecordsExistence = buildCheckRecordsExistence('Users', 'userId')

app.get('/users/:userId', usersCheckRecordsExistence, (req, res) => {
    const record = req['userId']
    res.send('在 GET /users/:userId 中', record)
})

app.put('/users/:userId', usersCheckRecordsExistence, (req, res) => {
    const record = req['userId']
    res.send('在 PUT /users/:userId 中', record)
})

app.delete('/users/:userId', usersCheckRecordsExistence, (req, res) => {
    const record = req['userId']
    res.send('在 DELETE /users/:userId 中', record)
})

// 这里不需要使用检查记录
app.post('/users/:userId', (req, res) => {
    res.send('在 POST /users/:userId 中')
})

// 这里不需要使用检查记录
app.get('/users', (req, res) => {
    res.send('在 GET /users 中')
})

这个解决方案的关键是您拥有可重用的代码,可以选择在何处使用它。

对于多个资源,您可以附加额外的中间件,如下所示。

const usersCheckRecordsExistence = buildCheckRecordsExistence('Users', 'userId')
const projectsCheckRecordsExistence = buildCheckRecordsExistence('Projects', 'projectId')

app.get('/users/:userId/projects/:projectId', usersCheckRecordsExistence, projectsCheckRecordsExistence, (req, res) => {
   
})
英文:

express builds the req.params in steps according to the path provided to it.
When you use a middleware on the root app (app.use(...)) middleware is unaware of the :param that is set downstream.

Example:

const express = require(&#39;express&#39;)
const app = express()
const port = 3000

// use middleware before all routes
app.use(printParamsMiddleware)

// use middleware before a specific routes
//                              |
//                              v
app.get(&#39;/users/:userId&#39;, printParamsMiddleware, (req, res) =&gt; {
  res.send(&#39;in GET /user/:userId&#39;)
})

app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`)
})

function printParamsMiddleware(req, res, next) {
    console.log(&#39;print params&#39;, req.params)
    next()
}
curl localhost:3000/users/1234

output

Example app listening on port 3000
print params {}
print params { userId: &#39;1234&#39; }

From the example you can see that the req.params is set after its passes through a route with a declared path.


Solution Suggestion

I suggest you create a middleware factory that generates a custom middleware per resource

// buildCheckRecordsExistence.middleware.js
function buildCheckRecordsExistence(modelName, paramKey) {
    return async function printParamsMiddleware(req, res, next) {
        const Model = mongoose.model(modelName);
        const recordId = req.params[paramKey];

        const record = await Model.findById(recordId);
        if (!record) {
            return next(new ErrorResponse(
                new Error(message.notFound(modelName)),
                404
            ));
        }
        req[paramKey] = record;
        next();
    }
}
// users.controller.js

// create a check records middleware specific for Users
const usersCheckRecordsExistence = buildCheckRecordsExistence(&#39;Users&#39;, &#39;userId&#39;)

app.get(&#39;/users/:userId&#39;, usersCheckRecordsExistence, (req, res) =&gt; {
    const record = req[&#39;userId&#39;]
    res.send(&#39;in GET /users/:userId&#39;, record)
})

app.put(&#39;/users/:userId&#39;, usersCheckRecordsExistence, (req, res) =&gt; {
    const record = req[&#39;userId&#39;]
    res.send(&#39;in PUT /users/:userId&#39;, record)
})

app.delete(&#39;/users/:userId&#39;, usersCheckRecordsExistence, (req, res) =&gt; {
    const record = req[&#39;userId&#39;]
    res.send(&#39;in DELETE /users/:userId&#39;, record)
})

// no need to use check record here
app.post(&#39;/users/:userId&#39;, (req, res) =&gt; {
    res.send(&#39;in POST /users/:userId&#39;)
})

// no need to use check record here
app.get(&#39;/users&#39;, (req, res) =&gt; {
    res.send(&#39;in GET /users&#39;)
})

The key in this solutions is that you have a reusable code and you can select where to use it.

and in case of multiple resources you can append an additional middleware like so.

const usersCheckRecordsExistence = buildCheckRecordsExistence(&#39;Users&#39;, &#39;userId&#39;)
const projectsCheckRecordsExistence = buildCheckRecordsExistence(&#39;Projects&#39;, &#39;projectId&#39;)

app.get(&#39;/users/:userId/projects/:projectId&#39;, usersCheckRecordsExistence, projectsCheckRecordsExistence, (req, res) =&gt; {
   
})

huangapple
  • 本文由 发表于 2023年6月19日 00:55:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76501645.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定