-
We already proposed the middleware API in the architecture RFC, but we need to flesh out a few more details before building. Middleware is very high priority since it's a blocker for auth, so let's hash this out quickly.
|
Beta Was this translation helpful? Give feedback.
Replies: 15 comments 1 reply
-
Curious if it would make sense to have a way to specify middleware for a whole folder/page? I'm imagining with an authentication app there's commonly unauthenticated and authenticated pages, so you wouldn't want to add auth middleware globally, but it might be a pain to add it to each individual endpoint. |
Beta Was this translation helpful? Give feedback.
-
Good point @dajinchu. Pages will be authenticated at the component level, so middleware won't directly affect that. I think authentication is fine as a global middleware because authorization will be handled inside queries/mutations. The so if a user isn't logged in but needs to be, the query will throw the Authorization error, not the middleware. Folder level middleware could easily be added later. So it's not critical for now, but definitely good to consider this. |
Beta Was this translation helpful? Give feedback.
-
You might be thinking this just not sure if this is clear here but we may want to short circuit the response in the middleware so we should ensure we have access to both req, res and next just like connect middleware. |
Beta Was this translation helpful? Give feedback.
-
Maybe this is weird but I think it would be good to be able to put middleware on a query by query basis too like express. Not sure how to do this syntactically ( maybe decorators?) |
Beta Was this translation helpful? Give feedback.
-
Last thought from me: is there any way we can remain compatible with connect middleware for this or is that crazy talk/a dumb idea? |
Beta Was this translation helpful? Give feedback.
-
When would it truly be better for middleware to short circuit the request vs in the actual query/mutation?
If I understand what you are saying, my proposal includes this by exporting middleware from the query/mutation file
🤷 I don't know — what does everyone thing about this? |
Beta Was this translation helpful? Give feedback.
-
Access control / special redirect rules for api routes / cache middleware
👍 |
Beta Was this translation helpful? Give feedback.
-
Alright, in absence of any good reason to not be Connect middleware compatible, I think that's probably the correct approach. Initially I didn't really consider this because of how we are abstracting away the http layer. But I think we'd probably regret not being connect compatible. I think that would also mean we don't need both Looks like we can run the middleware ourselves without |
Beta Was this translation helpful? Give feedback.
-
Doesn't seem composable if you have multiple middlewares that are shared and you want to apply to the query. Perhaps allow the query export to be an array of [middlewareA, middlewareB, queryFn]? import {Context} from 'blitz/types'
import db, {FindOneProductArgs} from 'db'
import acl from 'blitz-acl-middleware'
import cache from 'blitz-cache-middleware'
async function getProduct(args: FindOneProductArgs, ctx?: Context) {
// Can do any pre-processing here or trigger events
const product = await db.setUser(ctx.session.user).product.findOne(args)
// Can do any post-processing here or trigger events
return product
}
// export an array of middlewares followed by the query or mutation
export default [acl({ resource: 'products' }), cache({ maxAge: 10 }), getProduct] |
Beta Was this translation helpful? Give feedback.
-
So remember what we are doing here. We aren't building a traditional REST API that's tightly coupled to HTTP specific stuff. We're building an RPC system where extremely little logic lives in the HTTP layer. This is quite similar to GraphQL. GraphQL and RPC both abstract away the HTTP layer. You never think about an HTTP status code with GraphQL, for example. Both GraphQL and our RPC layer always take a POST request and return the same status code for most responses. I think HTTP middleware is mostly anti-composable. It's more like mixins where where you don't know exactly what, where, or why something is happening. Placing business logic, like access control, in the HTTP layer makes your queries and mutations much harder to test. They are no longer plain JS functions, but full-on HTTP request handlers. Maybe I'm totally crazy and missing something obvious (in which case please enlighten me! 😅), but I think 100% of business logic in Blitz apps should live inside query and mutation functions. Not in HTTP middleware. I view Blitz HTTP middleware as a rare escape hatch for things that are impossible to do inside queries/mutations. The perfect example is session management which needs to read and set cookies. But that's all session management middleware will do. The session object will be passed into queries/mutations in the context object where access control will take place. That makes queries/mutations very nice to test because it's a plain JS function that accepts a plain session object. Nothing HTTP specific at all is needed. These queries/mutations are also very composable simply by passing along the context object. All without any reliance on HTTP specifics. Decorators could be a good way to wrap queries/mutations with access control. If someone needs a more traditional REST approach with lots of HTTP specific stuff, then they can do that with custom API routes. What do you think? |
Beta Was this translation helpful? Give feedback.
-
Great point |
Beta Was this translation helpful? Give feedback.
-
I like the idea of using something like |
Beta Was this translation helpful? Give feedback.
-
Update!I'm working on implementing connect compatible middleware. With three additions:
Query/mutation execution will itself be a middleware. This will be ran after all custom middleware. Here's example code I'm working towards: // app/queries/getReferrer.ts
import {Middleware} from 'blitz'
const customMiddleware: Middleware = async (req, res, next) => {
res.blitzCtx.referrer = req.headers.referer
await next()
console.log('Query result': res.blitzResult)
}
export const middleware = [customMiddleware]
export default async function getReferrer(data, ctx) {
return ctx?.referrer
} Global Query/Mutation MiddlewareCan add middleware to // blitz.config.js
const authMiddleware = require('./somewhere')
module.exports = {
middleware: [authMiddleware],
webpack: (config) => {
return config
},
} |
Beta Was this translation helpful? Give feedback.
-
HTTP middleware is added in #652! |
Beta Was this translation helpful? Give feedback.
-
@flybayer I'm a little concerned about the concept of local middleware. I really liked when I saw this comment:
I was wondering how one would go about using queries/mutations from places other than the frontend. For example, if a background worker needs to make a mutation, would it have to call the middleware itself? I suppose it could build out context objects and pass those into the query/mutation function. Another concern that I have is about the type safety of the query/mutation functions. What is the type of Thanks! |
Beta Was this translation helpful? Give feedback.
HTTP middleware is added in #652!