#16 | Middleware & Grouping Routes - AdonisJS 7

Adocasts| 00:14:07|Apr 10, 2026
Chapters19
Middleware are functions that intercept HTTP requests before they reach the route handlers, enabling pre-processing and control over the request lifecycle.

AdonisJS 7 makes middleware and route grouping clear: create server/router/named middleware, measure requests with Luxon, and group or prefix routes for clean, scalable apps.

Summary

Adocasts walks through practical middleware patterns in AdonisJS 7, showing how to create a request-logging middleware and register it at server, router, and named levels. The presenter demonstrates how to use the kernel.ts file to register middleware and how the next() function chains middleware in a “mountain” like flow. Luxon is used to timestamp requests to measure duration, and logs reveal how long each request takes. We see how changing middleware type affects behavior: server-level middleware runs for all requests, router-level runs only for matched routes, and named middleware is applied only to routes that opt in. The lesson then shifts to grouping routes with route groups and prefixes, plus using router.resource for concise RESTful routing. Finally, the tutorial introduces early bailouts in middleware to stop a request before it reaches a route handler, illustrated with a bail=true query-string example. All of these techniques are demonstrated on a Challeges resource and its associated routes, underscoring best practices for clean, maintainable AdonisJS apps with middleware-driven logic.

Key Takeaways

  • Server-level middleware runs for every incoming request regardless of route matching, and is registered in kernel.ts under the server use section.
  • Router-level middleware runs only for routes defined inside a router group, while named middleware applies only to routes that explicitly opt in.
  • A request-logger middleware uses Luxon to capture start and end times and logs the HTTP method and URL along with the duration in milliseconds.
  • Route groups allow applying middleware to an entire set of routes and support a shared prefix, simplifying route definitions and maintenance.
  • The router.resource method can define all standard RESTful routes for a resource in one line, with options to apply middleware to specific actions.
  • Middleware can bail out a request early by returning a response before it reaches the route handler (example: bail=true in the query string).
  • Using route prefixes and empty string behavior in groups helps keep route definitions DRY while preserving correct URL structure.

Who Is This For?

Essential viewing for AdonisJS developers who want to master middleware types, route grouping, and resource routing in version 7, plus those who want practical tips for logging, timing, and early bailouts.

Notable Quotes

""Middleware are functions that intercept the incoming HTTP requests before they reach the route handler.""
Introductory definition of what middleware does in AdonisJS.
""This next function is what instructs Adonjs to move on from this middleware to the next one.""
Explains how the middleware pipeline progresses.
""If we jump back into our browser, hit refresh, everything should still continue to work.""
Demonstrates that a bailout or grouping change preserves functionality.
""Middleware can bail out of this request by returning a response early.""
Shows how to stop a request before hitting a route handler.
""Using route groups, we can apply the same middleware to all routes inside the group in one line.""
Highlights the efficiency of route groups for middleware application.

Questions This Video Answers

  • How do I register server vs router middleware in AdonisJS 7?
  • Can I apply middleware to all routes with route groups or do I need to assign it individually?
  • What is the difference between server, router, and named middleware in AdonisJS 7?
  • How do I use router.resource for RESTful routing in AdonisJS 7?
  • How can I bail out a request early from middleware in AdonisJS 7?
AdonisJS 7Middleware typesServer middlewareRouter middlewareNamed middlewareKernel.tsLuxonRoute groupsRoute prefixesRouter.resource','RESTful routes'
Full Transcript
Middleware are functions that intercept the incoming HTTP requests before they reach the route handler. They allow you to perform actions like authentication checks, logging, modifying requests or responses along with much much more. Each middleware can pass the request to the next middleware by calling the next function. Modify the request before passing it along to whatever is next. modifying the response before passing it back to the user and stop the pipeline early with a response as well. A good way to showcase this is to make a new middleware and we can easily do this using the ACL's make middleware command to log how long a request takes to process. So we'll call this request logger. When we hit enter to run this command, it's going to ask us which type of middleware we want to register. And there's three different types. So we have server, router, and named server middleware is going to run for each and every request entering into our application regardless of whether or not a route has been matched against the requests. Router middleware will only run for each and every request hitting our defined routes within our application. And named middleware will apply only to routes we specifically add the middleware to. For right now, let's go ahead and select server for this middleware. And then we're going to walk through the other two with this same middleware as well. And that will give us a good sense of how this is also getting registered. So we'll hit server there and it's going to create our request logger middleware as well as update our start kernel.ts file. If you'll recall back to where we were walking through our project structure, the kernel.ts file is where the various types of middleware get registered inside of our application. We can take a look at this really quick as a reminder. So we have our server use for our server level middleware, router use for our router level middleware, and then the exported middleware section for our named middleware as well. And whenever we create a middleware, it's going to get added within our app middleware directory under the name that we gave it. So request logger middleware here. As you can see within here, we have access to our HTTP context, similar to our route handlers. And there's also this next function right here as well. This next function is what instructs Adonjs to move on from this middleware to the next one. When our route runs out of middleware, it'll then move on to the route handler inside of the pipeline. Once the route handler is done, it will work its way backwards back through all of our middleware, running anything we've defined after the next function, kind of like a mountain. So before we call the next function is on the way up with our request, working its way to our route handler. anything after that next function is on its way back to the user after our route handler has already been executed. So we can kind of demonstrate this by tracking how long it takes to get from before our next function to after our next function. So at the start of our handle method, which is what's inevitably called whenever Adonisjs executes our middleware, we can do const start equals and then use Luxen datetime, which is a wrapper around each JavaScript object to give us plenty of utilities and just overall easier usages with dates inside of JavaScript. So we can do datetime now to get the current date and time. And since inside of ourv we have our time zone set to UTC, that's the time zone Luxen will use for our date time across our Adonis.js JS code. Okay, so we have our start time that we're fetching before our next function. After our next function, then we can go ahead and grab our end time using the same date time now to grab the time at the end of our next functions call. And then we can grab the duration by doing end.de passing in the start to get the difference between our end and start. And since this is going to be relatively small, we can grab this in milliseconds. And we can do two fixed two to ensure that the decimal value isn't too long. Once we have that, we can use our HTTP context to log information. And we'll do ctx request method to grab the requests HTTP verb that's being used. So get postput patch delete. And then we can also log out the ctx request URL as well so that we know what's being requested. do a hyphen and then plop our final duration in as milliseconds. Okay, let's give that a save. Then since this has already been registered as server level middleware, it will automatically be applied to any of our routes inside of our application. So we can do mpm rundev to boot this back up. We'll leave our terminal open here so that we can inspect it. Let's jump back into our browser. Hit refresh to submit a request. Jump back into our terminal and we can see vr our info log right here that for our get/challenges request it took 53 milliseconds. And that's time it took to get from our start down to our end. And then we have this additional log up here just logging out our HTTP context because I left this particular section in. We can go ahead and get rid of that. We don't need that logged out. So since this is server level middleware, it's going to run for any request hitting our server regardless of whether or not a routes been found for it. So, if we enter in not a real URL and hit enter to submit that, we're going to get an error here. But our middleware is still going to see this request come through. And since this is the last one in our server middleware, it's actually going to be the last middleware to run before we get our exception. So, if we jump back into our terminal here, we're still getting our get not a real URL, and it took 19 milliseconds despite us not actually having a route definition for that path. So, let's go ahead and jump back into our start directories kernel.ts file and move our request logger middleware from our server use section down into our router use section. This is going to switch this particular middleware from being a server level middleware to a router middleware changing that type that we had selected whenever we first created it. So, we'll hit save there. Our server is going to reboot because this is a boot process level change that we have made. So, it's going to clear out our terminal there. But we can jump back into our browser and if we resubmit a request for our not a real URL now and we jump back into our terminal, you'll notice that we don't see anything because our request logger isn't actually run at all since router level middleware are only run for routes matched inside of our route definitions. So it's a small difference there between our server and router level middleware, but an important one nonetheless. And just to verify that this is still working, we can go ahead and switch this back to our /challenges path. check out our terminal one more time. And sure enough, there it is right there. Still getting logged out for our router middleware since a route has now been matched. Finally, if we move this from our router middleware level down into our named middleware, we're again switching the type of middleware that this is. And in addition to that, the definition structure is a little bit different as well. What we need to do is provide a key name to assign the particular middleware here being used. So we can give it the name of request logger. And now this middleware will only be applied to routes where we assign the request logger to that route definition. So right now if we were to give this a save, we've changed this from a router level middleware now to a named middleware. Meaning that if we jump back into our browser and give our challenges URL a refresh there, jump back into our terminal, we're not going to get anything logged because we haven't assigned this middleware to any of our routes yet. So it won't automatically apply itself. We need to manually apply it to specific routes that we want it to run for. And we can easily do this jumping back into our route definitions. And let's go ahead and scroll on down to / challenges by applying the use method. This accepts a single middleware or an array of middleware if we need to apply multiple. In our particular case, we just need to apply this one. And then we can import the middleware that's being exported from that kernel.ts file. And off of here, we can reference the particular named middleware that we want to apply to this route, calling it like a method. So if we apply our request logger there, give that a save. You can see, okay, I didn't even need to jump back into our browser, it automatically just refreshed and sent off a fresh slashchallenges request for us. So, we can see it's back to working now since we have now applied it to our/challenges route. If we jump into the show route though, we'll see that we don't get any new logs because we don't have it applied to that particular route definition. If, for example, we wanted this middleware to be applied to all of our challenges routes, we could individually apply the use middleware for each one of those, kind of like this. However, there's a cleaner option if you prefer to just have these defined in one spot for each, and that's called route groups. So, I'm going to go ahead and hide my terminal away here. We can do router dot, and there is a group method that we can use that accepts a callback function. any routes that we define inside of this callback function will get applied to this route group. So if we take all of our challenge routes here, give them a select and hit option up, we can move these inside of this route groups callback function and these route groups apply similar to our individual route definitions in terms of applying things like middleware. We can chain off of it the use method. add in our middleware. Request logger. And now in this single line, we have applied our request logger middleware to each of the routes inside of this group because we're now applying it at the group level instead of an individual route definition level. Our red squiggies at this point are just formatting. So if I give this a save, it will reformat there. Getting rid of those. So now if we open up our terminal one more time, you'll see, okay, our browser's already gone ahead and done a refresh. So we're now getting a log here for our show page as well. Fantastic. We can also take groups a step further as well by extracting the /challenges out of each of these route definitions as well and moving that to the group level using a prefix designation. This allows us to define a prefix to all of the route definitions defined inside of the group. So that could be our slash challenges here. Meaning that we can now get rid of the slash challenges for each one of these individual route definitions. Simplifying the patterns for each. We can do this easily by holding option command and hitting the up arrow in Visual Studio Code to get multiple cursors going. Hit option right arrow to jump to the end of the word. Option shift left arrow to jump to the start of the word and then hit delete to get rid of that selection leaving us with just this. Now in Adonjs, if we leave an empty string like this, it will automatically be inferred that it's going to behave like a slash. So you can either leave an empty string there or you can put a slash if you prefer that. And the same goes for any of these as well. We can just do our route parameter next. And they'll be joined appropriately. I'm going to go ahead and leave these as individual slashes just like so. And we can now give this a save. Jump back into our browser. Give it a refresh just for sanity sake. And everything's still working. AOK. Okay. If we jump back into our index page, that still works too. If we get rid of that dangling slash, still gets picked up just fine as well. If we go into create a new challenge, still working there. So, you can see everything's still being applied the exact same as we had it before. We've just now cleaned up our route definitions a little bit further. In addition to that, for each of those requests that we made, we're also getting a log logged out for the time that the request took to get between the start and end of our next function with our request logger middleware. Okay, I think we're now ready to go ahead and condense this route group down into just a single resource. If you recall a couple of lessons ago, we introduced resources at the route level. So, instead of doing all of our route group right there, we can simply do router.resource, resource provide in the name of the resource which is our challenges and then the controller that should handle the resource. So our controllers.challenges controller that one line automatically defines each of these individual route definitions for us in addition to the destroy route definition that we do not yet have defined here. So we can go ahead and get rid of all of this right there and dangle our use method off of our router resource. You will notice though that our use method is now showing a red squiggly and that is because we need to specify two arguments now instead of just the one. The first argument that we need to provide in addition to the actual middleware that we want to apply is which actions inside of the resource the middleware should be applied to. In our case, if we want these to be applied to all of the definitions inside of this resource, we can merely just provide in a star to signal that and it will apply to all route definitions inside of this resource. The same as we had just had it with our group route definition. As you saw, we can also apply this on an individual route basis though by specifying the specific route name inside of the resource that we want this to apply to. Now, if on the offshoot we only wanted this middleware to apply to our get requests inside of this resource, we could do an array here instead and apply this to our index, show, and edit pages just like so. And at this point, the squiggly is only because of formatting again. So, if I give that a save, we're all good to go. So, these route definitions that came with our starter kit should be starting to make sense to you. However, what they're actually doing inside of the route handlers is still a little above our heads. We will get familiar with those though as we continue onward. For now, we have one more thing to cover with our middleware. So, if we jump back up to our request logger middleware, prior to the call of our next method, we can stop a request in its tracks and send a early response before it ever reaches our route handler using middleware as well. So, up at the top of our handle function here, we can say if we have on our request a query string, and remember we can use the input method to get at that. This will merge both the body and query string together, allowing us easy access to it. So if we have a query string with bail inside of it, we can go ahead and bail out of this request by returning a response early. And we can return whatever we would like here. Uh for simplicity sake, let's go ahead and just return JSON with the message bailed out. Let's hit save there. And now if we jump back into our browser, hit refresh, everything should still continue to work. Okay. But if we jump into our URL here and add in a query string with bail equals true set inside of it, hit enter. We're now going to get our bailed out message. Meaning our request was stopped in its tracks right here. It did not continue onward. It stopped right here and started its return, never reaching our route handler and sending back the user explicit message of bailed out. If we omit the bail inside of our query string, that if is never truthy and we continue through our request flow as usual.

Get daily recaps from
Adocasts

AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.