Closed ghost closed 6 years ago
@IGZgustavomarin you can use inject
to have Awilix build the middleware:
const { createController, inject } = require('awilix-express')
// Assuming `cacheMiddleware` is a factory function
const { cacheMiddleware } = require('./your/cache/middleware')
class TodoController {
constructor ({ todosService }) {
this.todos = todoService
}
getAll(req, res) => {
return res.send(this.todosService.getTodos())
}
}
module.exports = createController(TodoController)
.get('/todos', 'getAll', {
before: [ inject(cacheMiddleware) ]
})
Ok, this way works, but my middleware gets created each time I call the endpoint. Even if I set the lifetime to SINGLETON
. I'm doing this:
const cacheProducts = ({ config }) => {
return (req, res, next) => {
/** cache logic here, return memory data or return next() **/
}
return cache
}
cacheProducts[RESOLVER] = {
lifetime: Lifetime.SINGLETON
}
module.exports = cacheProducts
Is is possible to inject the same middleware every time? Otherwise, it never hits my cache data.
FYI: All this was working on previous version with code like this:
classs ProductsRouter {
/** code **/
this.router = Router()
this.router.get('/products',
cacheProductsMiddleware, // cache layer
inject('getAllProducts'), // data access layer
this.getAll // <- the endpoint handler
)
Lifetimes only apply to registrations, not resolvers built directly with container.build
(which is what inject
does), so you want to wrap it to use the registration:
module.exports = createController(TodoController)
.get('/todos', 'getAll', {
// assuming your middleware registration is named "cacheService"
before: [ inject(({ cacheService }) => cacheService) ]
})
In fact, Awilix has a resolver for this called aliasTo
which does exactly what the previous example does:
const { aliasTo } = require('awilix')
module.exports = createController(TodoController)
.get('/todos', 'getAll', {
// assuming your middleware registration is named "cacheService"
before: [ inject(aliasTo('cacheService')) ]
})
Hope this helps. 😄
Ok, now the middleware gets injected each time. But the cache layer makes some patching to the Response
object that doesn't work with inject
, meaning it never cache anything. I think the middleware function that inject
actually adds, collides with the apicache
(npm module) that I'm using.
Is there a way to create a controller and register the routes differently?
But the cache layer makes some patching to the Response object that doesn't work with inject, meaning it never cache anything
How so?
How do you register cacheService
? It would have to be singleton. Also, that cacheProducts
middleware has return cache
as well as return (req, res) => ...
, is that a typo in the example?
Could you try creating a minimal reproduction repository? That would make it way easier for me to help you, cause I'm certain we can make it work. 😄
Finally solve it. It turns out that the cache layer didn't work with HEAD
request only and that was messing my example. It works as intended. Sorry about that!
I've separated the logic into two controllers, like this:
mainRouter
.use(loadControllers('cache/*'))
.use(loadControllers('products/*'))
Each of those folders contains a class wrapped with a createController
call. But I noticed that on each request a new instance is created. This is too expensive for the cache layer (ex: connection, warmup, etc). Is it possible to make that instance a singleton?
My cache controllers is as this:
class ProductsCache {
constructor ({ config }) {
/** THIS IS CALLED ON EACH REQUEST **/
this.cacheAll = apicache.options({
...(config.cache.defaultConfig),
enabled: config.cache.defaultConfig.enabled === 'yes'
})
.middleware(config.cache.expiration.productList)
}
deleteAll = (req, res) => {
res.send(apicache.clear('/api/products'))
}
}
module.exports = createController(ProductsCache)
.get('/api/products', 'cacheAll')
.delete('/api/products/_cache', 'deleteAll')
I tried as a function instead of class, but it also gets called on each request. Can that behavior be changed?
The whole point is for the controller to be instantiated with each request, so you can inject request-specific stuff in the constructor. 😄
Seems to me you want the apicache
to be a singleton.
// In your container config
const apicache = require('apicache')
container.register({
productCache: asFunction(
({ config }) =>
apicache
.options({
...(config.cache.defaultConfig),
enabled: config.cache.defaultConfig.enabled === 'yes'
})
.middleware(config.cache.expiration.productList)
)
.singleton()
})
// Controller
class ProductsCache {
constructor ({ productCache }) {
this.cacheAll = productCache
}
deleteAll = (req, res) => {
res.send(apicache.clear('/api/products'))
}
}
module.exports = createController(ProductsCache)
.get('/api/products', 'cacheAll')
.delete('/api/products/_cache', 'deleteAll')
Thanks, I did something similar to that. :tada:
I'm setting up a controller with
awilix-express
, it works well, but I have some doubts.In this example below, is it possible to setup a middleware on
before
that access the instanceTodoController
?Is there a way to do that? Maybe changing approach and not using a class? Can you show an example please? Thanks!