Cleaning Up Routes with Controllers
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.
- Author
- Tom Gobich
- Published
- Jan 31
- Duration
- 4m 52s
Developer, dog lover, and burrito eater. Currently teaching AdonisJS, a fully featured NodeJS framework, and running Adocasts where I post new lessons weekly. Professionally, I work with JavaScript, .Net C#, and SQL Server.
Adocasts
Burlington, KY
Transcript
Cleaning Up Routes with Controllers
-
[music]
-
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
-
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,
-
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.
-
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.
-
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.
-
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.
-
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.
-
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,
-
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.
-
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
-
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.
-
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.
-
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,
-
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.
-
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.
-
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.
-
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
-
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,
-
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.
-
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,
-
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
-
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,
-
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.
-
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.
-
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,
-
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.
-
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.
-
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.
-
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.
-
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.
-
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,
-
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.
-
We're just now using controllers to make our lives a little bit easier.
-
Introduction
-
Fundamentals
-
2.0Routes and How To Create Them5m 23s
-
2.1Rendering a View for a Route6m 29s
-
2.2Linking Between Routes7m 51s
-
2.3Loading A Movie Using Route Parameters9m 17s
-
2.4Validating Route Parameters6m 6s
-
2.5Vite and Our Assets6m 38s
-
2.6Setting Up Tailwind CSS9m 5s
-
2.7Reading and Supporting Markdown Content4m 32s
-
2.8Listing Movies from their Markdown Files8m 51s
-
2.9Extracting Reusable Code with Services7m 4s
-
2.10Cleaning Up Routes with Controllers4m 52s
-
2.11Defining A Structure for our Movie using Models9m 38s
-
2.12Singleton Services and the Idea of Caching6m 11s
-
2.13Environment Variables and their Validation4m 16s
-
2.14Improved Caching with Redis10m 44s
-
2.15Deleting Items and Flushing our Redis Cache6m 46s
-
2.16Quick Start Apps with Custom Starter Kits6m 28s
-
2.17Easy Imports with NodeJS Subpath Imports8m 40s
-
-
Building Views with EdgeJS
-
3.0EdgeJS Templating Basics8m 49s
-
3.1HTML Attribute and Class Utilities6m 9s
-
3.2Making A Reusable Movie Card Component10m 24s
-
3.3Component Tags, State, and Props4m 53s
-
3.4Use Slots To Make A Button Component6m 56s
-
3.5Extracting A Layout Component5m 13s
-
3.6State vs Share Data Flow2m 59s
-
3.7Share vs Global Data Flow6m 7s
-
3.8Form Basics and CSRF Protection6m 13s
-
3.9HTTP Method Spoofing HTML Forms3m 3s
-
3.10Easy SVG Icons with Edge Iconify7m 57s
-
-
Database and Lucid ORM Basics
-
4.0Configuring Lucid and our Database Connection4m 3s
-
4.1Understanding our Database Schema9m 35s
-
4.2Introducing and Defining Database Migrations18m 35s
-
4.3The Flow of Migrations8m 28s
-
4.4Introducing Lucid Models5m 43s
-
4.5Defining Our Models6m 49s
-
4.6The Basics of CRUD11m 56s
-
4.7Defining Required Data with Seeders11m 11s
-
4.8Stubbing Fake Data with Model Factories13m 48s
-
4.9Querying Our Movies with the Query Builder15m 30s
-
4.10Unmapped and Computed Model Properties3m 24s
-
4.11Altering Tables with Migrations7m 6s
-
4.12Adding A Profile Model, Migration, Factory, and Controller2m 57s
-
4.13SQL Parameters and Injection Protection9m 19s
-
4.14Reusable Query Statements with Model Query Scopes8m 11s
-
4.15Tapping into Model Factory States9m 15s
-
4.16Querying Recently Released and Coming Soon Movies4m 59s
-
4.17Generating A Unique Movie Slug With Model Hooks7m 59s
-
-
Lucid ORM Relationships
-
5.0Defining One to One Relationships Within Lucid Models5m 49s
-
5.1Model Factory Relationships2m 54s
-
5.2Querying Relationships and Eager Vs Lazy Loading5m 17s
-
5.3Cascading and Deleting Model Relationships5m 16s
-
5.4Defining One to Many Relationships with Lucid Models6m 56s
-
5.5Seeding Movies with One to Many Model Factory Relationships5m 24s
-
5.6Listing A Director's Movies with Relationship Existence Queries8m 41s
-
5.7Listing and Counting a Writer's Movies8m 41s
-
5.8Using Eager and Lazy Loading to Load A Movie's Writer and Director5m 18s
-
5.9Defining Many-To-Many Relationships and Pivot Columns9m 48s
-
5.10Many-To-Many Model Factory Relationships4m 50s
-
5.11A Deep Dive Into Relationship CRUD with Models18m 5s
-
5.12How To Create Factory Relationships from a Pool of Data13m 55s
-
5.13How To Query, Sort, and Filter by Pivot Table Data9m 47s
-
-
Working With Forms
-
6.0Accepting Form Data12m 15s
-
6.1Validating Form Data with VineJS9m 29s
-
6.2Displaying Validation Errors and Validating from our Request7m 16s
-
6.3Reusing Old Form Values After A Validation Error2m 3s
-
6.4Creating An EdgeJS Form Input Component5m 28s
-
6.5Creating A Login Form and Validator5m 1s
-
6.6How To Create A Custom VineJS Validation Rule9m 7s
-
-
Authentication & Middleware
-
The Flow of Middleware7m 49s
-
Authenticating A Newly Registered User4m 14s
-
Checking For and Populating an Authenticated User2m 10s
-
Logging Out An Authenticated User2m 24s
-
Logging In An Existing User6m 54s
-
Remembering A User's Authenticated Session6m 55s
-
Protecting Routes with Auth, Guest, and Admin Middleware5m 36s
-
-
Filtering and Paginating Queries
Join The Discussion! (9 Comments)
Please sign in or sign up for free to join in on the dicussion.
Jean
Hi,
When I save routes.ts file, imports remain identical. Do we have to do anything to make it work?
Please sign in or sign up for free to reply
tomgobich
Hi Jean!
The standard import for controllers will work just fine, for example
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
Please sign in or sign up for free to reply
Jean
For mystical reasons, I missed that part. Sorry :D
By the way, the new version of Adocasts is cool.
Please sign in or sign up for free to reply
tomgobich
No worries at all, Jean!! :D
Thank you, I really appreciate that!!
Please sign in or sign up for free to reply
frp
Hi Tom!
I'm trying to inject the context into a controller, and it is not working:
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?
Please sign in or sign up for free to reply
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.
Here's an example of binding the HttpContext to a service and then the service to a controller.
Please sign in or sign up for free to reply
frp
Ok thanks. That might do what I want just as well.
Please sign in or sign up for free to reply
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.
Please sign in or sign up for free to reply
gabriel-moraes
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.
Please sign in or sign up for free to reply