Playing Next Lesson In
seconds

Transcript

  1. So in the last lesson, we helped make our code a little bit more dry by extracting reusable code out into our services. In this lesson, we're going to learn how we can clean up our routes even more

  2. by extracting our handlers out into what's called a controller. So let's go ahead and start by actually creating our controller. And just like we used the ACLI to create our service,

  3. we can use it too to create a controller. So let's jump back into our terminal, stop our server with Ctrl+C, clear our terminal out, and let's do node.ace.list to see the list of commands once more.

  4. Since we're looking to make a file, we'll want to look within the Make section. And right here, we can see Make Controller. There's a number of different things that we can actually pass into this command, so let's view the help options for it.

  5. So let's do node.ace.make.controller-help. So just like we have already with our services, we can provide a name in after we type out the command to name our underlying controller.

  6. But then after the name, we can optionally add in any actions or methods inside of that controller that we want the controller to have. In addition to that, there's some options that we can add in as well.

  7. We can specify that we want the name of the controller to be singular. We can make this controller a resourceful controller, which we talked about resources whenever we learned about the HTTP methods that we can use to define our different routes.

  8. So this would be a controller that's automatically created having a method stubbed for each one of those different route options that we covered within that lesson. And then there's also an API variant for that as well,

  9. where it would be created for a resourceful API route. For our case, let's focus on the handlers that we have already. So we'll do node.ace.make.controller.

  10. We'll call this movies, and then we'll want an index method and a show method on it. So let's go ahead and create this. And now within our app directory, we should be able to find a folder

  11. called controllers with a file called movies controller inside of it. So let's jump back into our text editor. And sure enough, there's controllers, and there's our movies controller.

  12. If we open it up, we see a class called movies controller with an index and a show method stubbed out, both of which are ready to accept in the HTTP context.

  13. So for the most part, we should be able to jump into our routes, copy the handler as we have it, cut it out, jump into our controller. That was the index method that we copied the contents for,

  14. so we'll paste it within the curly braces here. The red squiggly on our movie service is just because we need to import it, so we'll type movie, go down the movie service, and hit tab to auto-import it.

  15. And then down here, we have ctx. However, within the controller, they have it stubbed to an object, which we can then use to extract view directly out of the HTTP context object.

  16. So now instead of ctx.view, we could just do view.render. Let's stop here and give this a save. It's going to format it for me, and let's jump back into our routes.

  17. And now what we need to do is replace our handler portion and tell it to point to our controller instead. We can pass in an array where the first argument

  18. is the controller class that we want to use. So this would be our movies controller. I'm going to tab to auto-import that. And then we can do comma,

  19. and the second argument will be the method within there that we want to use. And you'll notice we get a nice autocomplete that allows us to see the methods that we have within the file itself.

  20. So we'll tell it to use the index method there. We can give this a save, and you'll see something nice happen right up here. Our controller import actually went from importing directly, as you see here,

  21. to, if I give it another save, importing lazily. So now the import is behind an arrow function, meaning that Adonis won't actually import the file

  22. until we attempt to use it, which, as we get more and more controllers, will actually help our server boot up just that much more quicker. So at this point, we can go ahead and jump back into our browser,

  23. and it looks like we might have forgot to boot the server back up. So let's go ahead and open up our terminal. Yep, sure enough, npm run dev here. Okay, that looks good. Let's give the page a refresh now.

  24. Okay, cool. So there we go. We still get the same result. The only difference is now the route handler is going through our controller rather than directly inside of our route.

  25. And as you can see here, having just a single line for a route definition is a lot more readable and maintainable. And so multiply this times 50, as we build out different routes for our application,

  26. this becomes a heck of a lot more readable and easier to maintain than if we had 50 of these within here. So let's go ahead and move our show path over into our controller as well.

  27. So we'll just copy everything within the route handler, cut it out, switch over to our movies controller, scroll down to our show method, paste it in between the curly braces, scroll down a little bit more.

  28. Looks like we'll need our params and our view from our HTTP context. So we get a view and params, get rid of the ctx. That's prefixing those. So there, there, and we have one more right there.

  29. And then we just need to import the toHtml method. I'm going to jump back into our routes file, scroll up to the top, and just give it a copy. Jump back into our movies controller, jump up to the top, and give it a paste.

  30. So now if we scroll back down to our show method, everything should be nice and happy. Actually, now that I look at it, we don't have a reason to have this share separated. So we can move this state into the state directly within our render call, just like so.

  31. Give this a save, jump back into our routes. We can get rid of all of these unused imports now. So those and that. Replace the route handler here with an array,

  32. pointing to our movies controller, and specifically the show method within it. Give that a save, and there we go. So now if we jump back into our browser, everything should still work.

  33. We're just now using controllers to make our lives a little bit easier.

Cleaning Up Routes with Controllers

@tomgobich
Published by
@tomgobich
In This Lesson

We'll learn what controllers are and how they can be used to drastically simplify our route definitions by allowing us to move our route handlers off the route definition and into the controller.

Join the Discussion 10 comments

Create a free account to join in on the discussion
  1. @Jean

    Hi,

    When I save routes.ts file, imports remain identical. Do we have to do anything to make it work?

    1
    1. Responding to Jean
      @tomgobich

      Hi Jean!

      The standard import for controllers will work just fine, for example

      import MoviesController from '#controllers/movies_controller'
      Copied!

      However, mine changed to lazy style imports on save within the video due to the ESLint I set up and configured code actions for within VS Code in lesson 1.4 of this series.

      Below is an example of the ESLint rule error that's auto-fixed by my code action configuration when I save my routes.ts file.

      ESLint is completely optional! It essentially is a style guide for your code, and anything not matching the style guide will display an ESLint error.

      If you'd like to use it, you can install the ESLint extension within VS Code. Then, you can have it auto-fix ESLint errors by adding the below within your VS Code settings.json

      "editor.codeActionsOnSave": {
        "source.fixAll.eslint": "explicit"
      } 
      Copied!
      1
      1. Responding to tomgobich
        @Jean

        For mystical reasons, I missed that part. Sorry :D

        By the way, the new version of Adocasts is cool.

        1
        1. Responding to Jean
          @tomgobich

          No worries at all, Jean!! :D

          Thank you, I really appreciate that!!

          0
  2. @frp

    Hi Tom!

    I'm trying to inject the context into a controller, and it is not working:

    import type { HttpContext } from '@adonisjs/core/http'
    import { inject } from '@adonisjs/core'
    
    @inject()
    export default class JoinController {
      ctx: HttpContext
    
      constructor(ctx: HttpContext) {
        this.ctx = ctx
      }
    }

    I got this error message:

    Cannot inject "[Function: Object]" in "[class JoinController]

    Sorry about the massive font lol. It looks like in the docs but does not work. Any ideas?

    1
    1. Responding to frp
      @tomgobich

      Hey frp! When a route calls a controller's method as its handler the HttpContext is automatically provided as their first parameter, so you shouldn't need to inject the context into a controller.

      That said, somewhere Harminder Virk explained this well, but I can't seem to find it. The error though is because the HttpContext can't be bound in this context. If you were to bind it to a service, and then bind the service to your controller, all would work.

      export default class MoviesController {
        async index(ctx: HttpContext) { // <--
          return ctx.view.render('pages/home')
        }
      }
      Copied!

      Here's an example of binding the HttpContext to a service and then the service to a controller.

      
      import { HttpContext } from '@adonisjs/core/http'
      import { inject } from '@adonisjs/core'
      
      @inject()
      export default class MovieService {
        constructor(public ctx: HttpContext) {}
      }
      Copied!
      • app
      • services
      • movie_service.ts
      
      import type { HttpContext } from '@adonisjs/core/http'
      import Movie from '#models/movie'
      import { inject } from '@adonisjs/core'
      import MovieService from '../services/movie_service.js'
      
      @inject()
      export default class MoviesController {
        constructor(protected movieService: MovieService) {}
      
        async index({ request, view }: HttpContext) {
          console.log(request.url())
          console.log(this.movieService.ctx.request.url())
          return view.render('pages/home')
        }
      }
      Copied!
      • app
      • controllers
      • movies_controller.ts
      0
      1. Responding to tomgobich
        @frp

        Ok thanks. That might do what I want just as well.

        0
        1. Responding to frp
          @tomgobich

          Anytime! Yeah, most of the time, I like to offload most of my business logic into services, keeping controllers clean to just handle the request flow.

          0
  3. Hello Master.

    When I try to make a request via the api, the message "Connection was refused by the server." is being returned.

    I changed the origin to '*'(config/cors.ts), but unfortunately I was unsuccessful.

    Access via http (brownser) works perfectly, but via rest api does not.

    0
    1. Responding to gabriel-moraes
      @tomgobich

      Hi Gabriel! Unfortunately, there isn't a straightforward answer here with the given information. Are your API requests being sent from inside your application or via a REST client? It sounds like your application works a-okay, your environment is just having issues connecting to it. This could be due to firewall settings or even WSL vs Non-WSL, if you're on Windows. Or, you may just need to use 0.0.0.0 instead of localhost if you're using a REST client.

      0