Tom Gobich
@tomgobich
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
- Member Since
- Jan 10, 2021
- Lessons Completed
- 57
- Comments Contributed
- 295
- Hours Watched
- 4.5
Recent Activity
Here's what @tomgobich has been up to this past year
-
Published lesson Patching Tag Changes for our Modules & Lessons
-
Published lesson Storing Module Order Changes from Vue Draggable
-
Published lesson Storing Lesson Order Changes & Handling Cross-Module Drag & Drops
-
Replied to You can also do it like this in FormDialog.vuedefineProps() const...
Wow - thanks a ton for sharing this, Jeffrey! I completely missed Vue adding in the
defineModel
macro, that's a huge improvement!! -
Upvoted discussion Selective mail transport driver installation in adonisjs
-
Replied to discussion Selective mail transport driver installation in adonisjs
Similar to Lucid, when you configure the AdonisJS Mail package, it'll ask you specifically which drivers you intend to use and it will only install those applicable to your selections.
-
Replied to I believe there has been an update. Now, it is possible to use...
Hi gribbl! Yep, absolutely!! It is officially documented within Lucid's documentation!! 🥳 A great new addition!
-
Replied to export default class User extends compose(BaseModel, AuthFinder...
Hi mojtaba-rajabi! I wouldn't recommend trying to redirect the user from inside a method on the model.
There's a high probability the redirect would get forgotten about should you ever need to debug
You'd still need to terminate the request inside the controller method/route handler.
If this is for AdonisJS 6, what I would recommend instead is to let the framework do its thing. The
findForAuth
method is called by theverifyCredentials
method. This method purposefully hashes the provided password, regardless if a user is found or not, so that there isn't a timing difference between the two. Reducing the ability for a bad actor to scrape your login form for credentials &/or valid emails using a timing attack.This
verifyCredentials
method then throws an exception if the login is wrong, which you can capture and display on the frontend. This exception is available via theerrorsBag
flash message. That covers yourif (!user)
check.Then, I would let
verifyCredentials
return back the user if one is found, regardless of theisActive
flag and instead check thatisActive
flag after verifying the credentials. That way:You can keep timing attack protections AdonisJS set for you in place
You know the user is correct, since they know their credentials, so you can guide them (if applicable) on how to activate their account.
You don't have any sneaky code doing things in places you might not expect a year or two down the road.
You can then easily pass along whatever message you'd like via Inertia using flash messaging.
public async login({ request, response, session, auth }: HttpContex) { const { uid, password } = await request.validateUsing(loginValidator) const user = await User.verifyCredentials(uid, password) if (!user.isActive) { session.flash('error', 'Please activate your account') return response.redirect().back() } await auth.use('web').login(user) return response.redirect().toRoute('dashboard') }
Copied!Hope this helps!
-
Published lesson Creating & Listing Sortable Course Lessons
-
Published lesson Editing & Deleting Course Lessons
-
Published lesson Adding A Publish Date & Time Input
-
Upvoted comment No worries, Thanks a lot Tom.
-
-
Replied to Hi,First, thank you for your video ! That's very clear. around...
Hi Pierrick, thank you for watching!
You can find this within lesson 1.1 - "What We'll Need Before We Begin." At the mark, we created the database "adonis6" for use in this series, I apologize; it seems I misspoke and said table in this lesson when I meant database.
-
Replied to Hi Tomgobich, thanks for your advice! It seems like verification...
Anytime! Yep, definitely some movement there!! 😊
-
Published lesson Model Query Builder Macros in AdonisJS 6
-
Replied to Copy/pasting your own remark here from lesson 2.17:As of TypeScript...
Thank you, Thomas!
-
Replied to Hi Tom, Thanks for the detailed response! 1. I was specifically...
Anytime!!
I'm fairly certain the browser displays the popup before the response of the request is actually received, at least that is the behavior I've noticed in the past with Firefox (the browser I use).
I, unfortunately, don't think or know of a way where is a way to enable/disable depending on success. I tested on Laravel Forge's site and still got the popup with invalid credentials (screenshot below). I can't say for certain it is impossible to do, but to the best of my knowledge I'm not aware of a way. Sorry, I know I haven't been much of a help here.
-
Replied to In AdonisJS v6, when using the access token authentication guard...
So long as the underlying data columns remain the same and the changes you're looking to do adhere to the type requirements set by the
@adonisjs/auth
package I don't think there would be any issues with reusing the table for other tokens. I don't foresee an enum column type causing issues either, so long as you includeauth_token
as one of the values in the enum.If you give it a try, let me know how it goes! 😊
-
Replied to Hi Tom, 1) Is it possible to prevent the browser from displaying...
Hi jabir!
This is a native browser behavior when forms are submitted with a password field. So, I suppose one way would be to not use the
type="password"
on the input, but I wouldn't recommend that as prying eyes could easily grab someones credentials. There's a long discussion on StackOverflow of some attributes that may do the trick, though I'm not sure which are working as of today since browsers tend to change behaviors like this over time.Is this with forward navigation or using the browser's back button? If it is with the browser's back button, I don't think there is much you can do as that restores the saved state of the page in the browser's navigation history. The behavior you see there is going to match any multi-page application. If it is forward navigation, then that shouldn't be happening and there is likely a state update issue somewhere in your code. Happy to help dig in if it is a forward navigation issue and you're able to share code.
-
Upvoted comment Perfect! I will definitely use the reflash().
-
Published lesson Querying & Listing Sortable Course Modules
-
Published lesson Creating, Editing, & Deleting Course Modules
-
Published lesson Deleting Courses
-
Published lesson Showing A Course's Details
-
Published lesson The Tag Selector
-
Upvoted comment Thanks to your reply, I understand why it wasn't working. In...
-
Replied to Thanks to your reply, I understand why it wasn't working. In...
Anytime! Awesome, I'm happy to hear you were able to track it down and get it all fixed up! 😊 Another alternative, if applicable to your use-case, is to reflash any messages. This will push the flash messages forward with a secondary redirection.
Flash → redirect
home
→ reflash & redirectfamily.show
export default class UserHasFamilyMiddleware { async handle({ auth, response, session }: HttpContext, next: NextFn) { if (auth.user && auth.user.hasNoFamily()) { session.reflash() return response.redirect().toRoute('family.show') } return next() } }
Copied! -
Replied to Thank you so much for making this feature available so quickly...
My pleasure, thank you for the suggestion! 😊
-
Upvoted discussion Is it possible to dowload courses videos to watch it offline?
-
Replied to discussion Is it possible to dowload courses videos to watch it offline?
Hi noctisy!
This is something I've been meaning to add, but kept slipping off my radar.
I'll preface by saying I'm not going to add download support for videos we only have hosted on YouTube, which are typically our older videos. Though that is technically possible, I'm sure that'd be against some ToS of theirs and I do plan on moving these videos over to our storage service anyways.
As for our newer videos, which I think fully encompasses all of our AdonisJS 6 lessons, I've just deployed out an update to add a download button for Adocasts Plus subscribers! You can find it just below the video. Again, this is brand new, so if you run into any issues please do let me know!
As a warning, a lot of our videos are uploaded as WEBM, and that is how they'll download as well. So, you'll need to use a video player that supports WEBM videos.
-
Published lesson Querying & Listing An Organization's Courses
-
Published lesson Creating A New Course
-
Published lesson Editing & Updating Courses
-
Upvoted comment thanks . i am using adonis 6 . '@ioc:Adonis/Core/Application'...
-
Replied to thanks . i am using adonis 6 . '@ioc:Adonis/Core/Application'...
Anytime! Ah - then it'll be
import app from '@adonisjs/core/services/app'
import app from '@adonisjs/core/services/app' class SocketioService { inProduction() { return app.inProduction } }
Copied! -
Upvoted comment Hello, Sorry if you covered this point in another lesson, but...
-
Replied to Hello, Sorry if you covered this point in another lesson, but...
Hi davidtazy!
Yeah, the toast system should be fully working by the end of this lesson. And, in fact, In lesson 5.2 Logging Out Users, we add in a
session.flash('success' Welcome to PlotMyCourse')
after successfully registering the user.Are you redirecting the user after registering them? Flash messages are made available for the next request and won't be applied to the current, so the redirect does serve a purpose here.
Here is the final RegisterController's Store method.
@inject() async store({ request, response, session }: HttpContext, webRegister: WebRegister) { const data = await request.validateUsing(registerValidator) // register the user await webRegister.handle({ data }) session.flash('success', 'Welcome to PlotMyCourse') return response.redirect().toRoute('organizations.create') }
Copied!If you'd like & are able, feel free to share a link to the repo and I can see if anything stands out.
Also, that's awesome to hear! I hope you enjoy AdonisJS and thank you for joining Adocasts Plus!! 😁
-
Completed lesson Logging Out Users
-
Replied to Thank you a lot for the clarification Tom! It was a bit confusing...
Awesome & anytime! I'm happy to hear things are clicking for you. If there is anything you feel would help make things clearer for the first go around, I'm all ears!! 😊
-
Replied to Thank you for this quick response. So if I understand correctly...
Anytime! Yes, sorry, we'll move our home page into our group protected by the
auth
middleware in the next lesson (6.0). So, that warning specifically on the home page will go away in the next lesson. -
Replied to thank for tutorial.how can add app instance when new() a singleton...
So long as you're importing and using this service in a context where the application has already been booted, then you should be able to just import and use
app
as it is a singleton.import Application from '@ioc:Adonis/Core/Application' class SocketioService { inProduction() { return Application.inProduction } }
Copied! -
Upvoted comment Hello Tom, First of all, thank you for all this amazing work...
-
Replied to Hello Tom, First of all, thank you for all this amazing work...
For the authenticated user to be populated you must inform AdonisJS to check for it. This saves the roundtrip to populate the user in cases where it isn't needed.
To populate the user, you have two options
authenticate
- Requires an authenticated user. If an authenticated user is not found, an exception is thrown.check
- Will check to see if a user is authenticated, and populate that user if so. The request goes on as usual if an authenticated user is not found.
In terms of middleware, you have three options
auth
- Will callauthenticate
and redirect the user to the login page by default if an authenticated user is not found.guest
- Will callcheck
and redirect the user, I believe to the home page, by default if an authenticated user is found.silent_auth
- Will callcheck
and progress with the request as usual.
So, you're most likely getting "the page isn't redirecting properly" because your authenticate on the login page is attempting to redirect the user to the login page, resulting in an infinite redirect loop.
You most likely will want to replace your
authenticate
on the login page with theguest
middleware and that should fix the redirect loop. Then, for internal pages, you can either useauthenticate
if the user must be authenticated to access the page,check
if the user may or may not be authenticated to access the page, or one of the corresponding middleware. -
Replied to Hello Tom, I'm used to working with Doctrine (ex Symfony users...
Hi noctisy!
With Lucid, relationships work with and directly read off of the model. So, if you omit the
userId
but define the relationship, you'll end up getting an exception.Though I don't know the core team's exact reasoning, I'd imagine it is so the relationship can make use of the column decorator's mutations to build out the relationship columns. It could also be for compatibility with hooks as well.
For example, you might want a different naming convention in the model than in the table.
export default class Profile { @column({ isPrimary: true }) declare id: number @column({ columnName: 'profile_user_id', // column name in db serializeAs: null, // omit from serialization (maybe it is confidential) }) declare userId: number @belongsTo(() => User) declare user: BelongsTo<Typeof User> }
Copied! -
Published lesson Reusable VineJS Exists In Organization Validation
-
Published lesson Sorting Difficulties with Drag & Drop
-
Published lesson Creating A Reusable Sorting Vue Component
-
Published lesson Replicating Behaviors for Access Levels & Statuses
-
Upvoted discussion TailwindCSS plugins installation issue
-
Replied to Hey! How can I send custom errors? I'm validating certFile and...
Hey Ruslan!
Yeah, as we'll discuss in lesson 3.6, covering Inertia's limitations, when Inertia sends a request it requires a specific format for the response. Otherwise, you'll get the error you mentioned above.
Because of this, Inertia doesn't want you to return 422 responses. Rather, they want you to flash the errors to the session and redirect the user back to the page.
So, if
certFile
is a form field, you could manually send back an error like this:async store({ inertia, request, response, session }: HttpContext) { // ... if (!certFile.isValid) { // not sure if certFile.errors is already an array or not // certFile type should be string[] for consistency with AdonisJS session.flash('errors', { certFile: [certFile.errors] }) return response.redirect().back() } // ... }
Copied!Otherwise, if you just need a general exception-like error, separate from validation, you could do:
async store({ inertia, request, response, session }: HttpContext) { // ... if (!certFile.isValid) { session.flash('errorsBag', { E_UNPROCESSABLE_ENTITY: certFile.errors }) return response.redirect().back() } // ... }
Copied!Hope this helps!
-
Replied to One other question I thought about as well is CSRF token validation...
No, CSRF is a broad reaching vulnerability across the web and is not specific to inertia nor server-side rendered applications. The goal of these attacks is to hijack a state changing request, causing an authenticated user to perform an action they did not want nor intend to happen.
You can go more in-depth on CSRF by reading through OWASP's guide.
-
-
Upvoted comment When it comes to setting up authentication with Adonis but in...
-
Replied to When it comes to setting up authentication with Adonis but in...
Yeah, if you're using session authentication using AdonisJS as an API, so long as the two apps share the same domain, the approach would still be the same just without the front-end routes & pages. Depending on your set up you may need to update the session cookie's domain config to allow subdomains.
In terms of validation, that's up to your discretion. You'll always want to perform server-side validation for data integrity's sake since client-side validation can be easily circumvented. Client-side validation is generally provided to improve user experience since it is a quicker way to provide feedback. When working with reactive client-side frameworks, it is generally considered good practice to validate on both the client & server side.
-
Replied to Hi Tom, could you explain why you are using actions over services...
Hi Emilien! Yeah, happily! Firstly, just want to point out that using either actions or services are perfectly acceptable options for the project we're building here. They're very similar, the primary difference being services are comprised of one class with many methods whereas with actions the service class is typically a folder and the methods are individual classes inside that folder for each action. So, actions just kind of scoot everything up a level.
With actions, since a class is dedicated to a single action, this means it is easier to split out sub actions, keeping things organized and easier to read by giving those sub actions names. I can't recall whether we've done this yet at this point in the series, but we most certainly do down the road!
Those separate classes also aide our ability to inject the HttpContext for specific actions. With services, that becomes a little more complicated and often leads to needing two different service classes (one with & one without the HttpContext injected). Whereas, with actions, we can easily make that determination on a per-action basis, which we do in this module (module 6) of the series.
Those two points are the primary deciding factors that lead me to use actions over services for this project. Actions may use more files, but that also brings with it more flexibility in how we write, organize, and inject things inside the project.
That wasn't a foresight thing either, I actually originally started this project with services (shown below) before switching to actions. Once I got to a certain level, I began feeling like actions would be the cleaner and less confusing way to go as opposed to having service methods daisy chaining other service methods to perform an action.
Hope that answers your question! 😊
-
Published lesson Updating Difficulties
-
Published lesson Confirming & Deleting Difficulties
-
Published lesson Replacing A Course's Deleted Difficulty
-
Upvoted comment Indeed it was this line of code static selfAssignPrimaryKey ...
-
Replied to Indeed it was this line of code static selfAssignPrimaryKey ...
Awesome, I'm glad to hear everything is working a-okay now! 😊
Anytime, happy to help! -
Replied to Issue created. First time I write an issue, let me know if something...
Looks good, thank you emilien! I'll try and take a look into it today after work.
-
Replied to Hi, I cannot share my project in a repo because it's a enterprise...
It might be something related to the UUID implementation. I haven't personally used them in AdonisJS so I'm not overly familiar with everything needed to get them working, but I do believe you need to inform AdonisJS you're self assigning the primary key via the model.
export default class User extends BaseModel { static selfAssignPrimaryKey = true // ... }
Copied!You can try digging into the
node_modules
to console log/breakpoint where AdonisJS is attempting to authenticate the user to see if:Is it finding the
userId
from the session okayIs it able to find the user via that id
Additionally, though this wouldn't cause your issue, both the
auth
middleware and theauth.check()
method calls theauth.authenticate()
method, so you only need one or the other.The auth middleware is great when you need the user to be authenticated to access the route. It will throw an error if the user is not authenticated.
The
auth.check()
call is great when the user can be authenticated but doesn't need to be to access the route.
Hope this helps!
-
Upvoted comment Hello, first thing first thanks for all the work you do for ...
-
Replied to Hello, first thing first thanks for all the work you do for ...
Hi Alan! Thank you for watching!
Are you able to share a link to the repository or a reproduction of it? Based on the two snippets, all looks good, but the issue could be in the user model, config, or middleware.
-
Upvoted comment Sorry to bother you again. Some of my model's columns are not...
-
Replied to Sorry to bother you again. Some of my model's columns are not...
Yeah, if you could create an issue that'd be great, thank you! Currently, this package reads the models as plaintext, line-by-line. So, there's room for error based on formatting. I'm hoping to refactor to instead use ts-morph which should fix those oddities up.
-
Upvoted comment Thank you Tom. Eternally grateful.
-
-
Published lesson The Confirm Delete Dialog & Deleting the Active Organization
-
Published lesson Listing & Creating Difficulties
-
Replied to Is a service just a controller?
Hi Luiz! Though they look similar and are traditionally both classes, they aren't the same as one another from a logical standpoint as they're in charge of different things.
Controllers are used to handle routes. AdonisJS will directly instantiate a new instance of a controller and call the appropriate method bound to the requested route.
Services generally aid controllers and events to help them complete their objectives. This could be something as simple as a helper, like normalizing text, or as complex as the main action of an operation, like storing a new blog post.
To put it in a real-world example, let's say you go to a dine-in restaurant. You sit at a table and the server comes over to take your order (determining which of the app's routes you need). They take your order (request) to the chef (our controller). The chef then is in charge of completing your order which may consist of:
Making a burger
Fetching fries
Grabbing that pickle slice
etc
Any one of those little things could come from a service, but the controller (the chef) coordinates and puts it all together to complete the request (order).
export default class DinerController { async order({ request, view }: HttpContext) { const data = await request.validateUsing(orderValidator) const burger = await BurgerService.getCheeseBurger() const fries = await FriesService.getMediumFry() const pickle = await CondimentService.getPickleSlice() return view.render('plate', { burger, fries, pickle }) } }
Copied! -
Published lesson Creating A UseResourceActions Composable
-
Published lesson Editing the Active Organization
-
Replied to No problem, don't apologize. Updated your package and everything...
Awesome! Glad to hear everything is working for ya now! 😊
-
Replied to Hi Tom, tried using your package to generate dtos, but it gives...
Hi emilien! Terribly sorry about that! I had missed normalizing the slashes where the model's file name is determined which resulted in the whole file path being used on Windows.
-
Published lesson How To Add Social Authentication with AdonisJS Ally & Google
-
Published lesson The Form Dialog Component & Adding Organizations
-
Published lesson Switching Between Organizations
-
Replied to Hello , There's a problem with the subtitles. It doesn't match...
Hi tigerwolf974! I'm so sorry about that. It looks like I accidentally uploaded the previous lesson's subtitles to this lesson. They should be all fixed up now. Thank you very much for the heads-up!!
-
-
Upvoted comment Awesome, that was it! Thank you!
-
-
Upvoted discussion App split into modules
-
Replied to discussion App split into modules
Hi David! Yeah, with AdonisJS you can configure it a ton to your liking, especially the
app
folder. Things inapp
are mostly imported and used in your code, rather than bound directly via AdonisJS, so you can configure the folder structure however you see fit.For inspiration, you might want to check out Romain Lanz's website. The
app
folder is structured very similarly to what you're after. -
Upvoted comment Found the issue, it happened because I forgot to add "await"...
-
Upvoted comment Hi, I'm not sure if there is something that I missed, somehow...
-
Replied to Found the issue, it happened because I forgot to add "await"...
Hi nonwiz! Happy to hear you were able to get it figured out! 😊
-
Replied to When changing over routes to:router.get('/register', [RegisterController...
Hi tdturn2!
First, your import is valid and will work, it just won't work with hot module reloading (HMR). HMR is enabled by default in newer AdonisJS 6 projects. Rather than fully restarting the application when a change is made in development, HMR enables the single updated spot to be updated on the fly.
AdonisJS uses NodeJS Loader Hooks to perform HMR, and loader hooks require dynamic imports to work and hot-swap modules when updated. If you'd like to read more on this, they walk through the full what & whys in the documentation.
So rather than importing your controller like this, which will not work with HMR:
import RegisterController from '#controllers/auth/register_controller' router.get('/register', [RegisterController, 'show'])
Copied!You can instead dynamically import the controller, which will work with HMR:
const RegisterController = import('#controllers/auth/register_controller') router.get('/register', [RegisterController, 'show'])
Copied!If you have your text editor fix lint errors on save, this change will happen automatically when you save your file. You'll see this happen for me in the lesson around the ESLint extension, and then adding the below to your JSON user settings.
mark. If you're using VSCode, you can turn this on by installing the{ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, }
Copied! -
Upvoted comment Thank tom!
-
-
Published lesson Setting & Loading the User's Active Organization
-
Published lesson Listing the User's Organizations
-
Upvoted comment Just wondering what's the clean way (if possible) to have a ...
-
Replied to Just wondering what's the clean way (if possible) to have a ...
Hi nonwiz! As we did with the service in this lesson, you can import and use enums directly inside your frontend. It'll then be bundled with the frontend code when you build it.
-
Upvoted comment How can i make it with adonisjs 6?
-
Replied to How can i make it with adonisjs 6?
The premise would be the same as shown in this lesson, just using the updated code for AdonisJS 6 authentication. You'd want to configure your admin and user guards in the auth config, pointing to the correct model in each guard. Include the
AuthFinder
mixin in both models. Then, it should essentially be the same login flow as any AdonisJS 6 app, just using the correct guard. -
Published lesson Adding the Remember Me Token
-
Published lesson Forgot Password & Password Reset
-
Upvoted comment By the way thank you for these videos you have great teaching...
-
Replied to By the way thank you for these videos you have great teaching...
That means a lot, thank you, tibormarias! Also, thank you for watching & being an Adocasts Plus member, it's greatly appreciated! Yeah, if you'd rather not use DTOs, I think that's a perfectly viable alternative approach.
-
Upvoted comment Hey Tom, thanks for fast reply. Actually i think i checked if...
-
Replied to Hey Tom, thanks for fast reply. Actually i think i checked if...
AdonisJS' automatic 302 to 303 conversion happens via the Inertia Middleware that comes with the package. You'll want to make sure it is configured as server middleware inside the
start/kernel.ts
file.I reckon the 302 issue might be related to your errors not originally populating. If you're able to and need further help digging into it, feel free to share the repo and I can take a look.
-
Upvoted discussion adocasts UI glitch
-
Replied to discussion adocasts UI glitch
Hey Terry! Sorry to hear you're having issues with the site!
We used to have a similar issue when we first introduced our mini-player, where the video you exited on would sometimes persist to the next lesson entered. However, this didn't impact titles, descriptions, or body content but rather just the loaded video.
I'll poke around and see if I can find anything that might cause something like this!
-
Replied to until this part i was amazed how flawlessly AdonisJS works with...
I agree, it does make things less DRY, but to my knowledge, in other Inertia-supported environments, you would need to define the type specifically for the frontend as well. At least with AdonisJS, we can convert it to the DTO on the server side to ensure what we're passing matches expectations on the frontend.
It would be awesome if we could directly access a serialized type of the model, but that would take quite the doing!
-
Upvoted comment Hey, my app doesn't provide validation errors right away. On...
-
Replied to Hey, my app doesn't provide validation errors right away. On...
Hey hsn! When you're redirecting, is the response type a 303? Inertia expects redirect statuses to be 303 to work properly, and the AdonisJS adapter should automatically take care of this for you. So, if that isn't happening then something is likely misconfigured in your project.
As for the
attr
section, if all items being passed into a page component are registered asprops
within the page, then there won't beattrs
to display as everything will be underprops
.attrs
in Vue captures non-registered prop data. For example:<template> <Header :user="user" :theme="blue" /> </template> ----- <script setup lang="ts"> // Header.vue defineProps<{ theme: string }>() <script> <template> <span>Hello</span> </template>
Copied!In the above
theme
is a registered prop, so it'll be underprops
, but notattrs
. Meanwhile,user
is not a registered prop, but is being passed into the header. Therefore, it won't be withinprops
, but ratherattrs
.That is the only thing coming to mind as to why the
attrs
may differ.Hope this helps!!
-
Upvoted comment Lesson 7 ✅
-
Upvoted comment Lesson 6✅
-
Upvoted comment Lesson 5 ✅
-
Upvoted comment Lesson four ✅
-
Upvoted comment Lesson 3 done ✅
-
Upvoted comment I have just started my first lesson
-
Upvoted comment Onto my second lesson… it's getting slightly complex but am ...
-
Replied to Onto my second lesson… it's getting slightly complex but am ...
Thanks for watching, Jony! Hope all is still going well!! 😁
-
Upvoted discussion Admin panel
-
Replied to discussion Admin panel
Hi Anjanesh! There isn't one built-in, but there are a couple of third-party options. AdminJS is compatible with AdonisJS, then there is another called Adonis Cockpit that is in pre-release.
-
Replied to Hi Tom, please can you do a section on how to log users in with...
Hi Carlos! Yes, I've been meaning to record a lesson on that! Perhaps I can get that recorded this upcoming weekend for release soon after.
-
Replied to You can also set your postcss directly in package.json and avoid...
That's awesome! Thank you for sharing, secondman!! 😁
-
Replied to It's been nearly a year since you published this so maybe you're...
Thank you, secondman!! Yeah, the folks in the YouTube comments quickly provided feedback when this lesson was released. My pronunciation should be fixed from, I believe, this lesson onward.
-
Replied to I installed IntelliSense as well but the suggestion when typing...
I believe that panel is called the "quick panel" in Intellisense. You may want to check through your VSCode User JSON settings to see if it may be disabled for you. You can find more info here:
https://code.visualstudio.com/docs/editor/intellisense#_customizing-intellisense
-
Published lesson Onboarding Newly Registered Users
-
Published lesson Logging In Users & Displaying Exceptions
-
Published lesson User Registration with InertiaJS
-
Published lesson Splitting Our Routes Between Auth & Web
-
Published lesson Logging Out Users
-
Upvoted comment which extension do you use for that autocomplete on vscode? ...
-
Replied to which extension do you use for that autocomplete on vscode? ...
Hey Chanbroset-Prach! I believe that should just be the default IntelliSense which comes with VSCode.
-
Published lesson Completing Our AppLayout & Navigation Bar
-
Published lesson Creating A Toast Message Manager
-
Upvoted comment cheers!
-
Replied to Thank you Tom for the very detailed answer, it just looks really...
Anytime! The grass can be a different shade of green depending on the ecosystem you're in, but just because it is different doesn't mean it's a bad thing. The migration approach is a common and popular choice, used in the like of .NET, Laravel, and likely others but those are two I know and have personally used.
Thank you, Hexacker! The one thing those two (.NET & Laravel) offer that AdonisJS doesn't is model generation, which is why I wanted to make a package to at least offer something in that regard. An approach for model/migration generation has been something planned by the core team, but it is likely still a ways out.
With model generation in play, the flow would go:
Define migrations → run migrations → generate models -
Upvoted comment I just have a question about defining the model. I'm coming ...
-
Replied to I just have a question about defining the model. I'm coming ...
Hey Hexacker!
In AdonisJS, migrations are what create our tables, foreign keys, indexes, etc. Migrations are great because they give us control to incrementally build or update our database as we need in the order we need. Most of the time, in starting an application, it'll seem tedious. But, as your application ages, they're fantastic for maintaining flexibility.
For example, if I have a pre-existing database with a
histories
table that holds two types of histories, view history and progression history. As the app ages, if I decide it'd behoove me to have this one table split into two, I can easily use migrations to create aview_histories
table and aprogression_histories
table. Then, in those same migrations, I can fill them directly with data from thehistories
table. Then, delete thehistories
table. These changes would take 3 migrations, which are run one after another at the same time my application with the needed code updates are deploying.create_view_histories_table
Creates theview_histories
table, then uses thedefer
method to populate it with pre-existing view histories from thehistories
table.create_progression_histories_table
Creates theprogression_histories
table, then uses thedefer
method to populate it with pre-existing progression histories from thehistories
table.delete_histories_table
Drops the no longer neededhistories
table
Models then, are strictly used to aide in CRUD operations inside our code. At present, there is no official way to generate migrations nor models. However, I do have a package in dev release to generate models from a database.
Fun fact, the migration example above is based on a refactor I did on the Adocasts site.
-
Replied to That was a better solution than mine.Thank you for the amazing...
Thank you for watching!! 😊 That's awesome to hear, thanks Hexacker!! I hope you enjoy your time with AdonisJS!! 😁
-
Upvoted comment Hi Tom - how to do the same setup with react
-
Replied to Hi Tom - how to do the same setup with react
Hi Carlos! Though I haven't worked with React since its class days (years ago), unless anything recently has changed, I don't believe React has a concept of global components. So, unfortunately, I don't believe this same global registration will work with React.
-
Completed lesson Global Components and Hydration Mismatch in Action
-
Upvoted discussion Add a webhook endpoint to AdonisJS web app | FIXED
-
Replied to discussion Add a webhook endpoint to AdonisJS web app | FIXED
Glad to hear you were able to get it figured out, Hexacker!
For anyone else who may come across this, you want to make sure you have CSRF disabled for the route, which is in
config/shield.ts
:/** * Configure CSRF protection options. Refer documentation * to learn more */ csrf: { enabled: env.get('NODE_ENV') !== 'test', exceptRoutes: ['/stripe/webhook'], // 👈 add your webhook route(s) here enableXsrfCookie: true, methods: ['POST', 'PUT', 'PATCH', 'DELETE'], },
Copied! -
Upvoted comment Are you using a specific icon theme in VS code? My .edge file...
-
Replied to Are you using a specific icon theme in VS code? My .edge file...
Hi Jeffrey! Yeah, I'm using the Bearded Icons file icon theme.
-
Upvoted comment Thanks Tom for the tutorial! I am trying to implement upload...
-
Replied to Thanks Tom for the tutorial! I am trying to implement upload...
Thanks for watching, Guy!
For that, I'd recommend using Drive (FlyDrive). Its goal is to provide a unified API regardless if you're working with the local disk, S3, GCS, etc. We, unfortunately, don't have any lessons on FlyDrive of yet but the documentation is pretty great! With it, the only thing you should need to change between local and production is that
DRIVE_DISK
environment variable and the APIs between local and S3 will be the same (bar any advanced situations).Drive is a wrapper around FlyDrive specifically integrating FlyDrive with AdonisJS. Both, written and maintained by the AdonisJS core team.
-
Published lesson Creating A Lucid Model Mixin for our Organization Relationship
-
Published lesson Seeding Our Initial Database Data
-
Published lesson Typing Lucid Models in Inertia with DTOs
-
Replied to Hello everyone,I recently had trouble connecting to my database...
Thanks for sharing Petzi!! 😊
-
Upvoted comment Thanks a lot. It work now.I'm going to continue to watch the...
-
Replied to Thanks a lot. It work now.I'm going to continue to watch the...
You're welcome, Nico! Happy to hear all is working now!
Hope you enjoy your time with AdonisJS & Edge! 😊
-
Replied to Thank you tom, it helped ! I'm actually following your courses...
That's awesome to hear, Petzi! 😊
Thank you for watching! I hope you enjoy your time working with AdonisJS!
-
Upvoted comment Hey, first, thanks for this full tutorial.I try to use route...
-
Replied to Hey, first, thanks for this full tutorial.I try to use route...
Hey Nico! Thanks for watching!
When you're passing props into your button component, you're already inside of an EdgeJS interpolation area. So, at that point you don't need the double-curly braces and adding them will actually mess things up in that context. Instead, you can directly access the route function, like below.
@shared.button({ href: route('movie.show', { id: movie.slug }), class: "" }) View Details @end
Copied!Also, and this might've just been formatting in the comment, but just in case also be sure the start and end tags for your EdgeJS components are on their own lines.
-
Upvoted discussion A Simple Framework Based on Edge.js
-
Upvoted discussion Which framework to start project ?
-
Replied to discussion Which framework to start project ?
Hi Petzi!
Real time data is going to be possible with either approach. As for which you should choose, that's really up to you. If you have a ton of dynamic elements, Inertia would probably be better. If it is mostly static content with some dynamic elements, then EdgeJS would likely be much easier.
The best way to answer this is to get hands on experience with them yourself, as everybody's opinions are going to be different. I'd recommend spending about a half hour to an hour with each to get a feel for them to help aide in your decision.
As for whether combining Inertia with Vue is a good choice, using Inertia would actually require working with Vue, React, Svelte, etc. Inertia is just a data broker between AdonisJS and those frontend client. If you choose to go with Inertia, which frontend client you choose out of those is completely up to you and your preferences. There's no superiority in the eyes of Inertia there. 😊
Hope this helps!
-
Published lesson Understanding Our Database Schema
-
Published lesson Defining Our Migrations & Foreign Keys
-
Published lesson Defining Our Lucid Models & Relationships
-
Upvoted comment It works! Thanks!
-
-
Replied to I am using AdonisJS 5 and using @inertia 0.11.1. I also enabled...
In order to use TypeScript your Inertia code is going to need a separate tsconfig, as the one AdonisJS uses isn't going to be compatible with it.
You may have luck trying to use one similar to the latest Inertia projects, or following an older guide we have on the subject.
I'd personally recommend just getting onto the latest versions though, you're going to have a much easier time.
-
Replied to This is exactly what I need, did a marathon on this series and...
Thanks so much, Fauzan!! I'm ecstatic to hear that! 😁
-
Replied to I should add I have installed @inertiajs/vue3 instead of '@inertiajs...
Are you using AdonisJS 5 or AdonisJS 6? Conversely, are you using Inertia 1 or an older Inertia version?
This series was released prior to Inertia 1's release and is meant for AdonisJS 5. The imports used throughout this series will reflect that accordingly.
We're currently in the process of releasing an up-to-date Inertia series, called Building with AdonisJS & Inertia. If you're not working with an older project, I'd recommend following that series instead of this one.
-
Replied to I've found the same error running "node ace make:model movie...
Hi Antoniogiroz! Please make sure you have Lucid installed and configure inside of your project.
npm i @adonisjs/lucid
Copied!Then
node ace configure @adonisjs/lucid
Copied!If you continue to have issues, please provide some more details or a repository with a reproduction of your issue.
-
Published snippet Accessing Lucid's Knex Connection Client
-
Published lesson The useForm Helper
-
Published lesson Common useForm Methods & Options
-
Published lesson Creating A FormInput Vue Component
-
Published lesson Cross-Site Request Forgery (CSRF) Protection in InertiaJS
-
Published lesson What Are Some of Inertia's Limitations
-
Upvoted comment Thanks for the response! the differentiation you pointed out...
-
Replied to Thanks for the response! the differentiation you pointed out...
Anytime!! Awesome, I'm happy to hear that! 😊 Should be discussed in the following two lessons:
4.8 - Setting Up A ToastManager
5.3 - Logging In Users & Displaying Exceptions*lesson numbers subject to change slightly
-
Upvoted comment Hi, thank you from Italy for this tutorial!! I have problem ...
-
Replied to Hi, thank you from Italy for this tutorial!! I have problem ...
Hi Davide! Rick Strahl's blog is a fantastic resource, especially for .NET devs! If you don't have any localhost projects making use of HSTS, you should be able to just clear out your browser's HSTS cache to get things working again. You can find AdonisJS' HSTS config within
config/shield.ts
.If however, you'd like to work with or need to work with HTTPS locally, you may have luck giving Caddy a try! I've never worked with it, but it automatically comes with and renews TLS certs for HTTPS and works on localhost.
-
Published lesson Specifying Page Titles & Meta Tags
-
Published lesson What Code Can & Can't Be Shared Between AdonisJS & Inertia
-
Published lesson Inertia Form Basics
-
Published lesson Form Validation & Displaying Errors
-
Upvoted comment I believe it should be noted that errors: (ctx) = ctx.session...
-
Replied to I believe it should be noted that errors: (ctx) = ctx.session...
Hi Thenial! That is correct,
errorsBag
won't be included when you specifically geterrors
. I opted to save this discussion for a later lesson, but the way I like to think of it is:errors
holds your validation errorserrorsBag
holds your exceptions
The
useForm
helper is really only going to be concerned with validation errors, as it works best when they have a field key, and exceptions tend to be more generic and not specific to any one field. Later in this series, we'll watch for theE_INVALID_CREDENTIALS
exception using an alert component. We'll also set up aToastManager
to automatically display a toast notification anytime an exception is reached. When we have validation errors, we'll also get anerrorsBag
entry with a summary of those validation errors, so a nice bonus of this approach is we'll also get a little toast when we have validation errors saying something like: "The form could not be saved. Please check the errors below."Additionally, the
inputErrorsBag
is, at this point, going to mostly be the same aserrors
. See:
https://github.com/adonisjs/session/blob/develop/src/session.ts#L360-L370 -
-
Replied to Hi, thanks for the reply! and that makes sense. If I choose ...
Anytime! Nope, I haven't tried it, but it should be perfectly fine! Just be sure to update the entry points within the below.
config/inertia.ts
resources/views/inertia_layout.edge
vite.config.ts
In your
config/inertia.ts
, there's a property calledentrypoint
you'll want to add to the base of the config that points to wherever you're client-side entrypoint is. Then, if you're using SSR, you'll also want to update the one nested inside thessr
property of the config.I'd reckon it'd look something like:
const inertiaConfig = defineConfig({ // ... the other stuff entrypoint: 'resources/js/app.ts', // client-side entrypoint ssr: { enabled: true, entrypoint: 'resources/js/ssr.ts', // server-side entrypoint }, })
Copied! -
Replied to Hi, what is the reason for having the inertia directory in the...
Hi Thenial! I don't know the exact reason, this is a change that was made in v1.0.0-19 pre-release. I believe it was done for cleanliness reasons. Since, you can have Inertia and non-Inertia pages, mix-and-matching both with them all within
resources
can lead to things being a little difficult to sift through. Having them completely separated does ease that a bit.I would add an asterisks next to that section of the docs specifically for Inertia assets. 😉
-
Replied to Thanks Tom! In my case, just if someone has the same problem...
Anytime!! Happy to hear you got it all working!
-
Upvoted comment Thanks Tom! In my case, just if someone has the same problem...
-
Upvoted comment There's some kind of error with this lesson video player.
-
Replied to There's some kind of error with this lesson video player.
Sorry about that, Luis, and thank you for the heads up! Should be all fixed now!
-
Published lesson Creating A Layout
-
Published lesson Default Layouts & Overwriting the Default Layout
-
Replied to Great tutorial, thanks! How can I validate dates? I get an error...
Hi Guy! Yeah, the VineJS date validation will return back a native JavaScript Date object. You can transform the value returned by the validator into a Luxon DateTime using the
transform
method.import { DateTime } from 'luxon' const validator = vine.compile( vine.object({ startDate: vine.date().transform((date) => DateTime.fromJSDate(date)) }) )
Copied! -
Replied to Hi Tom, When I initially created the adonis project, it didn't...
Hi Noctisy! You most likely skipped installing an auth guard during your project's creation (this is a new question during project creation that was added after we released our lesson). You can get this easily added in by installing and configuring the AdonisJS Auth package!
node ace add @adonisjs/auth --guard=session
Copied!https://docs.adonisjs.com/guides/authentication/introduction#installation
-
Replied to Hello!For installing AdonisJS, I used the command npm init adonis...
The command to create a new AdonisJS 6 project is
npm init adonisjs@latest
, the same command shown in this lesson. Try giving that a go instead. -
Published lesson Linking Between Pages & Page State Flow
-
Published lesson The Link Component and Programmatic Linking
-
Published lesson Global Components and Hydration Mismatch in Action
-
Published lesson Partial and Lazy Data Loading and Evaluation
-
Upvoted comment Hello / Good evening!I'm trying to install Adonis ts but I'm...
-
Replied to Hello / Good evening!I'm trying to install Adonis ts but I'm...
Hi Marley! These steps don't seem in line with an AdonisJS 6 installation, what command did you use to try and install this project? Also, what NodeJS version are you using? Note, AdonisJS 6 requires NodeJS v20.6 or greater.
-
Upvoted comment Thank you for the explanation!
-
-
Published lesson Setting Up TailwindCSS, Shadcn-Vue, and Automatic Component Imports
-
Published lesson The Flow of Pages and Page Props
-
Published lesson Sharing Data from AdonisJS to Vue via Inertia
-
Completed lesson What Is InertiaJS?
-
Published lesson What Is InertiaJS?
-
Published lesson What We'll Be Building
-
Published lesson Creating Our AdonisJS App With InertiaJS
-
Published lesson Server-Side Rendering (SSR) vs Client-Side Rendering (CSR)
-
Upvoted comment Hey Tom,Thanks for the answer !I will try both methods, yours...
-
Replied to Hey Tom,Thanks for the answer !I will try both methods, yours...
Anytime!!
Sounds good, best of luck!! 😊
-
Upvoted discussion Inertia and i18n
-
Replied to discussion Inertia and i18n
Hey Thomas!
The i18n module looks tightly coupled to the AdonisJS server, so I don't think this is or will be possible as the server is only in the picture on the initial load and only when using SSR. If you're not using SSR, all rendering takes place in the browser.
There said, I'm not sure it'd be needed. I'll preface by saying I haven't worked with the i18n module yet, but I have worked with others in an API-based environment. I believe you could complete this with something similar to what I've done in the past:
On non Inertia requests (no
X-Inertia
header), share the current locale code and that locale's translations with your inertia application and store it on the client (in a composable or store)
I'd recommend only sharing on non Inertia requests so that you aren't sending it with all requests as these translation files can grow quickly.When/if the user changes their locale, have this submit a form request using inertia to update the user's locale, and send back an updated locale code and that updated locale's translations to update within your client-side store.
Write your own little helper inside the composable/store that mimics the
t
method, accepting in the key of the string you're after, finding it from the stored translations, and returning it back.
Also note, you can reuse types from the i18n package to simplify things for any TypeScript support as well, just remember to use
import type
rather than justimport
.Looks like there's also an issue on the AdonisJS Inertia repo asking for i18n support. Again, I don't see direct support being something that'll happen, but thought I'd share in case you'd like to follow along or consider any of the options discussed there.
Hope this helps!
-
Replied to I'm particularly interested in learning more about the recently...
Hi cheeseburger!! I've actually been in the process of preparing and planning a series specifically on the latest AdonisJS 6 Inertia adapter! I've started recording and it should begin sometime within the next week or so. Though we'll be using Vue 3 within the series, Inertia's integration between Svelte and Vue should be rather comparable!
Thank you for the suggestion!!
-
Published lesson How To Make A Simple AdonisJS 6 Package
-
-
Replied to Not present by default in tsconfig.json
Hi news-zanndo! Yes, as of TypeScript 5.4, TypeScript will now automatically use the subpath imports defined within your
package.json
, meaning we no longer need to redundantly re-define them inside thetsconfig.json
.Apologies, I thought I had updated the body text for this lesson noting this, but evidently, I did not. Will do that now.
-
Upvoted comment Thank you, Tom. It was a great course!
-
-
Replied to Can you please tell me how to upload image in s3 in adonis js...
Answered on YouTube, but will answer here as well:
Hey there! The updated version of Drive is coming soon to AdonisJS 6, and it's called FlyDrive! As of now, the package is ready, they're just working to add the tight integration directly into AdonisJS 6. So, you can configure it and use it in AdonisJS 6 now, then swap to the direct implementation once it's released if you wish. Alternatively, you can always directly use the S3 SDK provided directly from Amazon. You can find the documentation for it here: https://flydrive.dev/docs/introduction
We do plan on doing lessons on FlyDrive in the future!
-
Published lesson Three Approaches for Organizing your AdonisJS Business Logic Operations
-
Upvoted discussion 301 redirects + web server
-
Replied to discussion 301 redirects + web server
Hi Miro! If I'm following correctly, depending on the desired behavior this could be set up as either a catch-all route or a middleware, something similar to the below should do. Also, AdonisJS uses good ol' NodeJS under the hood
node:http
.Catch All Route
Use a catch-all route when the redirects should not overwrite any of your defined AdonisJS routes.
You can add this route to the end of your AdonisJS route definitions that way if none of your application routes match the request, it'll be used.
// 👆 all other routes router.get('*', async ({ request, response }) => { const url = request.url() const redirectTo = 'https://duckduckgo' // 👈 check your api for a redirect if (!redirectTo) { return response.notFound() } return response.redirect(redirectTo) })
Copied!Middleware
You can either create it as an global HTTP or a named middleware.
Global HTTP will run on any matched route in your app.
Named middleware only runs on the routes you apply the middleware to
export default class RedirectMiddleware { async handle({ request, response }: HttpContext, next: NextFn) { const url = request.url() const redirectTo = 'https://duckduckgo' // 👈 check your api for a redirect if (redirectTo) { // 👇 stop the request here and redirect return response.redirect(redirectTo) } // 👇 continue with request to other middleware & route handler return next() } }
Copied!You can also optionally cache your API responses as you see fit as well so you don't have to relay back to it with every request.
Hope this helps!
-
Upvoted comment thanks for the reply, wouldn't it be better to use "preload...
-
Replied to thanks for the reply, wouldn't it be better to use "preload...
When working with many records and you can use
preload
, like in the example provided above, yes! -
Replied to Very excited to learn more about Adonis. Thanks for the course...
I'm happy to hear that Wasim! I hope you have fun and enjoy, and thanks for watching!
-
Replied to is there a risk of an n+1 issue when using lazy loading? ( first...
Hi laariane, great question!! Yes, n+1 is still possible, though unlike with other ORMs that offer auto lazy loading by reference since the loading is explicit with Lucid the n+1 issue becomes a little easier to spot.
For example, here's a n+1 query using Lucid's lazy loading
const movies = await Movie.all() for (let movie of movies) { // will loop through each movie and // individually query each movie's director await movie.load('director') }
Copied!This n+1 can be solved by switching to preloading or querying a list of directors rather than individually loading.
const movies = await Movie .query() // still queries all movies since we aren't filtering at all .preload('director') // loads director info without n+1
Copied! -
Upvoted comment Hi Tom, is there any way to do a polymorphic relationship? Or...
-
Replied to Hi Tom, is there any way to do a polymorphic relationship? Or...
Hey cbernard! Currently, there isn't a way to do polymorphic relationships with Lucid support. There are a couple of solutions you could use, maybe others as well.
Define a relationship column per relationship. If you only have a few tables needing the polymorphic relationship, you could just define an id column for each individual relationship, for example on
comments
you could have a nullablemovie_id
and a nullablecineast_id
column.
This is the approach I use for comments here on the Adocasts site.You could create a pivot table per relationship, for example, a
movie_comments
andcineast_comments
. In this case, don't think of it as a many-to-many, but rather a has many with an additional has one step. movie → has many comments (movie_comments) → has one comment (comments).
Hope this helps!
-
Replied to Hey, I noticed another thing, that the action buttons for comments...
Thanks so much for the heads up on this! Yeah, the
hoverOnlyWhenSupported
turns off classes using thehover:
Tailwind selector on touch devices. I've added alg
breakpoint to theopacity-0
on these comment actions. Should be all good now!Thanks again!
-
Upvoted comment Great, let's goo! Thanks you for your great work.
-
Replied to @tomgobich Hey, can you add keyboard support for the player?...
This has now been added!
Space = play/pause
Left arrow = skip backward
Right arrow = skip forward
Thanks again for the feedback!
-
-
Replied to Tom, thank you for quick response! It helped me a lot. I couldn't...
Anytime!! Yeah, I tried the same thing and had the same result. It's a hidden little bugger lol 😊
-
Upvoted comment Awesome
-
Upvoted lesson request Testing validators
-
Commented on request Testing validators
Thanks for the suggestion, Craigharman! Lessons on testing are definitely lacking in our current lesson library.
I was planning to do a series specifically on testing after our InertiaJS series, but perhaps I can move some things around to get some lessons out much sooner than that would've been.
I'll be sure to include testing validations as well! 😊
-
Replied to Hello, what about this in adonisJS v6 , there is no such a Normalizing...
Hi Danilesky17! Hmm, yeah it seems to have slipped from the documentation. Luckily though, it is still within AdonisJS, it's just now under the core helpers. Below is the example from this lesson updated for AdonisJS 6!
import { BaseModel } from '@adonisjs/lucid/orm' import { NormalizeConstructor } from '@adonisjs/core/types/helpers' export const WithExtras = <T extends NormalizeConstructor<typeof BaseModel>>(superclass: T) => { return class extends superclass { serializeExtras: boolean = true get meta() { return this.$extras } } }
Copied!import { compose } from '@adonisjs/core/helpers' import { WithExtras } from './mixins/serialize.js' import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm' export default class Topic extends compose(BaseModel, WithExtras) { @column({ isPrimary: true }) declare id: number @column() declare name: string }
Copied!Hope this helps!
-
Replied to Thank you for the video. I have few questions : Can we use decorators...
Hey Arthur! I just gave it a go and everything seems to be working a-okay with using decorators in a mixin! Only caveat is, it looks like you have to give the class a name and return it via that name on a separate line for TypeScript to be happy. Below is what I tried, note the imports are for AdonisJS 6, but the functionality would be the same between 5 & 6.
import { BaseModel, belongsTo, column } from "@adonisjs/lucid/orm" import { NormalizeConstructor } from '@adonisjs/core/types/helpers' import Organization from "#models/organization" import type { BelongsTo } from "@adonisjs/lucid/types/relations" export const WithOrganization = <T extends NormalizeConstructor<typeof BaseModel>>(superclass: T) => { class parentclass extends superclass { @column() declare organizationId: number @belongsTo(() => Organization) declare organization: BelongsTo<typeof Organization> } return parentclass }
Copied!- app
- models
- mixins
- organization.ts
import { DateTime } from 'luxon' import { BaseModel, column, hasMany } from '@adonisjs/lucid/orm' import type { HasMany } from '@adonisjs/lucid/types/relations' import Course from '#models/course' import { compose } from "@adonisjs/core/helpers" import { WithOrganization } from "#models/mixins/organization" export default class Difficulty extends compose(BaseModel, WithOrganization) { @column({ isPrimary: true }) declare id: number @column() declare name: string }
Copied!- app
- models
- difficulty.ts
As a heads up, you can define a parent/child relationship directly on the model without using compose. It'll look like the below.
export default class Taxonomy extends BaseModel { // other columns @column() declare parentId: number | null // parent taxonomy's id (null for top-level) @belongsTo(() => Taxonomy, { foreignKey: 'parentId', // use parentId to define parent relationship }) declare parent: BelongsTo<typeof Taxonomy> }
Copied!Hope this helps!
—-
Edit: Updated to account for the TypeScript requirement of the mixin class needing a name and for the return to be on a separate line. I missed the small red squiggly on the decorator when I first attempted this. -
Replied to discussion How can we handle task scheduling in Adonisjs
At present, the AdonisJS Core Team doesn't have an official scheduling package. I'd recommend checking out the adonisjs-scheduler package by Kabouchi! I tested it out a couple of weeks back and it seems to work really well. Plan on releasing a lesson on using it in the future.
-
Replied to Thanks. My node version is v21.6.1 also I tried npm cache clean...
If this isn't a brand new project, try giving this a go within a new project to see if you're having the same issue. If you are, it's likely something with your environment otherwise, it's possibly something with the project.
If it's something with your project, you can try the following
Delete the project's
node_modules
folderDelete the project's
package-lock.json
file (if this is not a new project be sure to have a version you can rollback to on Git should any version issues arise)Try running
npm install
to reinstall the dependencies
You may also try cloning the project into another location on your device as sort of a clean slate attempt.
Hope this helps!
-
Upvoted comment @tomgobich Hey, can you add keyboard support for the player?...
-
Replied to @tomgobich Hey, can you add keyboard support for the player?...
Hey Jahan! Our player does currently support keyboard shortcuts, but at present, they only work when the player's toolbar is focused. I'll look into expanding the shortcuts this week so they work when anything but an input is focused. Thanks for the feedback!
-
Published lesson Allowing Admins to Delete Movies and their Relationships
-
Published lesson Thank You for Watching!
-
Upvoted comment Hi Tom, I am new to adonisjs and template engines. I wonder ...
-
Replied to Hi Tom, I am new to adonisjs and template engines. I wonder ...
Hi Random123!
This isn't typically something Template Engines provide. Template Engines are really only concerned about generating markup and don't concern themselves with how that markup is used after it's generated.
You can look at potential third-party solutions to add this, though. I've never used it, but one that may solve what you're after is css-scope-inline.
-
Replied to This is the best resrource for dev's to learn AdonisJs and AdonisJs...
Thanks so much, Jawad! I'm happy to hear you're loving AdonisJS! 😊
-
Published lesson Posting Objects, Arrays, and an Array of Objects in HTML Forms
-
Published lesson Managed Transactions and Syncing Movie Cast Members
-
Replied to Thanks for pointing me to the source code. I have taken a look...
Looks like there's a previously reported issue with the same, I didn't look though the closed issues last time I checked so I missed it:
https://github.com/adonisjs/auth/issues/241
Sounds like it could potentially be related to how the UUID is defined as the issue creator reported switching that resolved his issue.If you'd prefer using MySQL, it may be worth looking into and, if it is related, potentially providing your reproduction repository within that issue.
-
Upvoted comment Very good example, is it still work in adonis 6?
-
Replied to Very good example, is it still work in adonis 6?
Thank you, Alands! The Model Hooks portion will still work a-okay as those didn't change much, if at all. However, Global Hooks will likely need some tweaking to get working due to changes with the container.
-
Upvoted comment Excellent course concept of Adonis
-
-
Upvoted comment Can I study course of API with Adonis?
-
Replied to Can I study course of API with Adonis?
Hi Jose! We will be doing a series of API development a little later on, at the moment though, we unfortunately don't have any resources for this. However, the primary difference between this series and an API is instead of providing information into page state, you'd instead return it as a JSON response.
-
Published lesson Uploading Movie Cover Images in our Create or Edit Form
-
Published lesson Using A Wildcard Route Param to Download Storage Images
-
Upvoted comment Hi. Thank you for the detailed explanation.I have been having...
-
Replied to Hi. Thank you for the detailed explanation.I have been having...
Hi Nnakwe!
Thank you for providing a repo! My best guess, after an initial look, is that you're probably running into an error at line 176 here:
https://github.com/adonisjs/auth/blob/develop/modules/access_tokens_guard/token_providers/db.ts#L176I don't have MySQL installed currently to be able to test it out though. When I get some free time I can give it a go. You may want to try debugging
result
at line 175 to see exactly what you're getting back. Line 175 is where it's inserting your token and returning back the inserted id. Since you said it's inserting okay, my best guess is thereturning('id')
isn't returning as expected for MySQL or your specific MySQL version.Could potentially be
Something specific to MySQL/MySQL2
The specific MySQL version you have installed on your machine
A bug in KnexJS
A bug in AdonisJS at line 176.
-
Upvoted discussion AdonisJs Session Installing error
-
Replied to discussion AdonisJs Session Installing error
Hey Mohammad!
If you happen to be using NodeJS v22, maybe try using v21 or v20 instead. There might be something breaking in the latest NodeJS versions.
It could also be a cache issue, in which case you can try
npm cache clean --force
-
Published lesson Allowing Admins to Update Movies and Clear Values
-
Published lesson How To Use One Form to Create or Edit Movies
-
Upvoted comment Thanks for the quick reply.It keeps working fine till node @...
-
Replied to Thanks for the quick reply.It keeps working fine till node @...
Anytime!!
It appears to be related to an issue in Vite, here: https://github.com/vitejs/vite/issues/17291
The error seems to be happening during the import of the
phIcons
, which also shares similar imports to those mentioned within the linked issue. For now, your best bet is to probably use a NodeJS version < 22, sorry!I tried updated all dependencies to latest, and the issue still persisted on NodeJS v22, hopefully it'll get fixed up soon!
-
Replied to works well after giving permission to npm to access .npm folder...
Oh, good find! Happy to hear you were able to get it figured out!
-
Published lesson Paginated Admin Movie Table
-
Published lesson Allowing Admins to Create Movies
-
Replied to Hey Tom! So I started a new project and it was working all good...
Hey omariqbal!!
Just pulled down at the same point you linked to test it out and everything seems to be running and working okay for me. What version of NodeJS are you running? I can double-check everything on that specific version as well. I'm on
20.14
at the moment. -
Upvoted comment ok, thank you
-
-
Upvoted comment I setup a project for adonisjs from scatch and it seems to be...
-
Replied to I setup a project for adonisjs from scatch and it seems to be...
Yep! Looks like they updated this with their recent Vite updates.
-
Upvoted comment yes, I'm using the latest node version 20.14. this is my repo...
-
Replied to yes, I'm using the latest node version 20.14. this is my repo...
Hmm… I just pulled down your repo, and ran
npm i
and it booted up a-okay. I'm using Node v20.14 as well. Maybe try deleting yourpackage-lock.json
andnode_modules
and rerunningnpm i
? Might've been some issue with NPM cache. -
Upvoted comment Does the import of external asset files (CSS, JS, and fonts)...
-
Replied to Does the import of external asset files (CSS, JS, and fonts)...
External assets as in from a CDN? No, you should just be able to plop the
<link/>
or<script>
for those directly within your EdgeJS page/layout.If the assets are being handled by Vite, then yes the process will be the same. You can also use the
asset()
helper to reach for specific files as well.https://docs.adonisjs.com/guides/basics/vite#referencing-assets-inside-edge-templates
-
Upvoted comment thanks for this tutorial, but I have an issue when running, ...
-
Replied to thanks for this tutorial, but I have an issue when running, ...
Hi Mathius! Is this a brand new AdonisJS project? If so, make sure you're using at least version
20.6
of NodeJS. If you are, can you please share the repo? I can take a look and see if anything stands out. -
Replied to Another question - how do you combine an access-token authentication...
Social Auth with AdonisJS forms the connection between AdonisJS and the 3rd party service and gives you the tools to discern who a user is. You can then use that info to create a user inside your application. From there, auth is done normally inside AdonisJS.
-
Upvoted comment Awesome Tutorial! Thank you. Just one thing: I get this error...
-
Replied to Awesome Tutorial! Thank you. Just one thing: I get this error...
Thanks Shahriar!
I would say to try restarting your text editor, sometimes it fails to pick up type changes. Apart from that, make sure your auth is configured correctly and double-check to make sure
accessTokens
is on your user model:static accessTokens = DbAccessTokensProvider.forModel(User)
Copied!If you're still having issues, please share a link to the repo or a repoduction and I can see if anything stands out!
-
Upvoted comment Thank you very much for the clear and detailed explanation! ...
-
Replied to Thank you very much for the clear and detailed explanation! ...
The expiry time should be included with the initial token, so you could store it as well on your client and check it every once in a while to see if time is running up. I would rely on the server though for actually discerning whether it's expired or not.
-
Published lesson Filtering, Preloading, and Sorting By Relationship
-
Published lesson Creating An Admin Layout
-
Published lesson Counting Stats for our Admin Dashboard
-
Replied to I use access tokens, and when I do either await auth.use('api'...
Hi Guy! The auth process shown in this series is for session authentication. The access token auth does not contain a
logout
method, but rather works via theaccessTokens
property added onto the User model.There are a few key differences between session and access token auth, you can check out our Access Token Authentication in 20 Minutes lesson to see a walk-through of auth for access tokens.
-
Replied to Hi Tom,Just wonder… the Edge docs encourages the use of components...
Hi Redeemefy!!
Although we're calling it as
layout()
, it is created and defined as a component! When you place your components withinresources/views/components
AdonisJS will automatically register those components as EdgeJS tags, which is what allows us to refer to our layout aslayout()
rather thancomponent('components/layout')
.Hope this helps!
-
Upvoted comment Why do you apply the guest middleware for each route instead...
-
Replied to Why do you apply the guest middleware for each route instead...
Hey Guy! I'm applying them here to the individual routes because, in a couple of lessons, we'll be adding a logout route that only authenticated users will be using. So
guest
won't apply to the whole group. -
Replied to Question, why I encountered error when I tried to make a request...
Hey Fadli! Are you using WSL by chance? If so, try giving
::1
in place ofhttp://localhost:3333
a shot. So, I think it'd be::
. You can also try enabling mirroring mode in WSL, noted here: 33/movieshttps://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking
-
Upvoted comment I am new to adonis, I think we need to pay attention more to...
-
Replied to I am new to adonis, I think we need to pay attention more to...
Thank you for watching, Fadli! I hope you find as much enjoyment in working with AdonisJS as I have! 😊
-
Replied to It looks, this config doesn't work "[edge]":{"editor.defaultFormatter...
Hi Fadli! Yeah, EdgeJS, at the moment, doesn't have formatting capabilities. Though there is an effort via a member of the community to get it added. I need to revisit a few portions of this lesson, one of which, is removing that line from my config as it doesn't do anything. Apologies about that!
-
Replied to Command "make:model" is not defined
Hi Amitava! Can you please provide some additional context around these errors? Did you clone this series' repo at a certain branch? Is this a brand new AdonisJS 6 project?
-
Published lesson Uploading and Displaying User Avatars
-
Published lesson Displaying A User's Profile
-
Published lesson Using Dependency Injection to Update A User's Profile
-
Published lesson Saving All Or Nothing with Database Transactions
-
Published lesson Persist Filters Easily with Lucid's Query String Method
-
Published lesson How to Create and Fix Missing User Profiles in Your Application
-
Published lesson AdonisJS 6 Access Token Authentication in 20 Minutes
-
Published lesson Allowing Users To Toggle A Movie As Watched
-
Published lesson Filtering By User's Watched Status
-
Published lesson Defining A Composite Unique Constraint
-
Commented on post How To Paginate Filtered Query Results
Hey all! Somehow, I completely missed that there's a
queryString
method available on the paginator that allows us to define additional query string key/values. We'll patch this in down the road, but wanted to make a note here that it is available!async index({ request, view, auth }: HttpContext) { const page = request.input('page', 1) const filters = await movieFilterValidator.validate(request.qs()) const movies = await MovieService.getFiltered(filters, auth.user).paginate(page, 15) const movieStatuses = await MovieStatus.query().orderBy('name').select('id', 'name') const movieSortOptions = MovieService.sortOptions -- const qs = querystring.stringify(filters) movies.baseUrl(router.makeUrl('movies.index')) ++ movies.queryString(filters) const rangeMin = movies.currentPage - 3 const rangeMax = movies.currentPage + 3 let pagination = movies.getUrlsForRange(1, movies.lastPage).filter((item) => { return item.page >= rangeMin && item.page <= rangeMax }) -- if (qs) { -- pagination = pagination.map((item) => { -- item.url += `&${qs}` -- return item -- }) -- } return view.render('pages/movies/index', { movies, movieStatuses, movieSortOptions, filters, pagination, qs, }) }
Copied! -
Upvoted comment cool, thank you for this tutorial.
-
-
Published lesson An Alternative Approach to Many-To-Many Relationships
-
Published lesson Toggling A Movie in an Authenticated User's Watchlist
-
Published lesson Listing and Filtering User Watchlist Items
-
Published lesson Validating Query String Filter Values
-
Published lesson How To Paginate Filtered Query Results
-
Published lesson Pagination First, Last, Next, and Previous Buttons
-
Replied to Hello Master.When I try to make a request via the api, the message...
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 oflocalhost
if you're using a REST client. -
Upvoted comment This is a great walkthrough on session based auth. One of the...
-
Replied to This is a great walkthrough on session based auth. One of the...
Thank you, Mark!! We haven't discussed social authentication with AdonisJS 6 in any of our lessons quite yet. But, once you get the user details from Google, you'll want to determine if you have a matching user already in your database, and if not, create that user. Once you've either found the matching user or created the new user, you can log them in using AdonisJS Auth.
router.get('/google/callback', async ({ ally, auth }) => { const google = ally.use('google') // ... validity checks const googleUser = await google.user() const appUser = await User.updateOrCreate({ // attempt to find a user with the matched Google Id googleId: googleUser.id }, { // no match found? merge googleId with this data and create the user // add any other data your user needs here email: googleUser.email, token: googleUser.token.token, refreshToken: googleUser.token.refreshToken }) // once we have the user, log them in await auth.use('web').login(user) })
Copied!Hope this helps!
-
Published lesson Filtering Our List by Movie Status
-
Published lesson How To Apply A Dynamic Sort Filter To Your Query
-
Published lesson Joining SQL Tables To Order By A Related Column
-
Published lesson Protecting Routes with Auth, Guest, and Admin Middleware
-
Published lesson Creating A Movie List Page
-
Published lesson Filtering A Query By Pattern Likeness
-
Published lesson Logging Out An Authenticated User
-
Published lesson Logging In An Existing User
-
Published lesson Remembering A User's Authenticated Session
-
Replied to After implementing slugify hook, I got an error, when refreshing...
Hi e4ma! I'm not all that familiar with SQLite, but if its default limit is a single connection or two, then that very well could've been the actual cause of your issue. This type of error can also occur due to a lingering SQL operation or a transaction that was left open.
-
Upvoted comment Many thanks for the effort you made to provide us with a very...
-
Replied to Many thanks for the effort you made to provide us with a very...
Thank you for watching, rnam!!
-
Published lesson The Flow of Middleware
-
Published lesson Authenticating A Newly Registered User
-
Published lesson Checking For and Populating an Authenticated User
-
Published lesson AdonisJS 6 Session Authentication in 15 Minutes
-
Replied to Nice listening with those Stripe web-hooks. I use those myself...
Adocasts has been my first time working with Stripe, we do more direct B2B sales where I work, so it's been a fun learning experience lol. :D
-
Upvoted comment It looks like the edge extension got merged into the AdonisJS...
-
Replied to It looks like the edge extension got merged into the AdonisJS...
Looks like it's still separated, though there is an AdonisJS Extensions Pack that includes all the AdonisJS-based extensions in one install
-
Upvoted comment Good content so far. Thanks for putting it together. Keep up...
-
Replied to Good content so far. Thanks for putting it together. Keep up...
Thank you, redeemefy!! I'm happy to hear it's helping, and best of luck with starting your business! You got this, just take it one line at a time!! :D
Also - apologies your Adocasts Plus badge was missing! Looks like it was a concurrency issue with the Stripe Webhook, I'll have to get that fixed up.
-
Published lesson Creating An EdgeJS Form Input Component
-
Published lesson Creating A Login Form and Validator
-
Published lesson How To Create A Custom VineJS Validation Rule
-
Replied to Interesting…. I'm coming from writing APIs with NestJS and this...
I'm not very familiar with NestJS so I, unfortunately, can't provide any direct comparisons, but under the hood, AdonisJS' query builder, for both the models and the database module, is a wrapping of the KnexJS query builder.
So, you do have access to an unrestricted query builder as well via the database query builder should you need it:
import db from '@adonisjs/lucid/services/db' await db.from('movies').select('slug', 'title').whereNotNull('released_at')
Copied!Just note that, unlike the model query builder, since the database query builder isn't going through models, naming strategies won't go into effect so you'll need to use the column names as they're defined inside the database.
Then, you also have the static model query methods as well for more straightforward queries.
-
Upvoted comment My assumption to this lesson is that the models.movies.query...
-
Replied to My assumption to this lesson is that the models.movies.query...
Yes, the model query builder will always provide a query scoped specifically to the model's table. If you don't specify a
select
within the query you're building, it will default to selecting all columns, which can be denoted by*
. So, some examples:await Movie.query() // Result: SELECT * FROM movies await Cineast.query().select('id') // Result: SELECT id FROM cineasts await Movie.query().select('slug').where('title', 'Interstellar') // Result: SELECT slug FROM movies WHERE title = 'Interstellar'
Copied! -
Published lesson Validating Form Data with VineJS
-
Published lesson Displaying Validation Errors and Validating from our Request
-
Published lesson Reusing Old Form Values After A Validation Error
-
Upvoted comment Thanks Tom.
-
-
Replied to Hello @tomgobich ,I am currently working with multi-level hierarchical...
Hi Bruce!
I'm going to preface this by saying, I'm not familiar with the closure table approach, though I did give the link a read-through.
That said, I'm not sure exactly how well this will translate to Lucid. You'll likely need to perform the actual insert as a raw query, and at that point, it may be easier to just define the entire thing as a raw query as well.
import db from '@adonisjs/lucid/services/db' await db.rawQuery(` INSERT INTO referrals(referrer, referee, level) SELECT p.referrer, c.referee, p.level + c.level + 1 FROM referrals p, referrals c WHERE p.referee = :refereeId AND c.referrer = :referrerId; `, { refereeId: 2, referrerId: 1 })
Copied!However, something like the below might work, at least for the select portion, if you're looking to keep with the query builder.
const records = await Referral.query() .join('referrals as child', 'referrals.referee', '=', 'child.referrer') .where('referrals.referee', 2) .where('child.referrer', 1) .select('referrals.referrer', 'child.referee', db.raw('referrals.level + child.level + 1 as level'))
Copied!I hope this helps!
-
Upvoted comment Hello Tom,Are you planning to cover automated testing in this...
-
Replied to Hello Tom,Are you planning to cover automated testing in this...
Hi tmuco!
Not specifically within this series, but we are planning to use the completed code from this series within another series to cover testing with Japa!
You can find what's on our radar at the page below, though series planned isn't in any particular order.
https://adocasts.com/schedule -
Upvoted comment Hi Tom,I've been porting what you did here to Adonis 6, but ...
-
Replied to Hi Tom,I've been porting what you did here to Adonis 6, but ...
Hi cbernard!!
Yeah, assuming you're using MySQL, it appears it doesn't accept zone information and that's the default way Luxon is serialized. It seems like AdonisJS takes care of this when the create/update flow goes through the model, but the model query builder looks to just be passing the update value directly through to KnexJS's update; hence why that particular attempt fails.
So, you should be able to fix your query builder update call by providing a valid MySQL date time format into the
expiresAt
value.await user.related('tokens').query().update({ // provide date time as string without timestamp offset expiresAt: DateTime.now().toSQL({ includeOffset: false }), })
Copied!Hope this helps!!
-
Published lesson How To Create Factory Relationships from a Pool of Data
-
Published lesson How To Query, Sort, and Filter by Pivot Table Data
-
Published lesson Accepting Form Data
-
Upvoted lesson request Managing Files
-
Commented on request Managing Files
Hi Bill! This sounds like a great topic to cover, thank you for the request!
We'll try to squeeze it in somewhere in between some of the Let's Learn AdonisJS 6 lessons.
-
Upvoted comment ou puis-je avoir les cours d'adocasts en français svp
-
Replied to ou puis-je avoir les cours d'adocasts en français svp
Salut! Bien qu’il n’y ait pas d’Adocasts officiels en français, comme je ne parle pas français, jetez un coup d’œil à Romain Lanz si vous ne l’avez pas déjà fait. Il a fait des diffusions en direct couvrant AdonisJS en français et est également un membre essentiel du framework.
Vous pouvez le retrouver sur YouTube et Twich.
— Translated with Bing Translate, apologies for any inaccuracies
Hi! While there is no official Adocasts in French, as I don't speak French, check out Romain Lanz if you haven't already. He's been doing live streams covering AdonisJS in French and is a core member of the framework as well.
-
Published lesson Defining Many-To-Many Relationships and Pivot Columns
-
Published lesson Many-To-Many Model Factory Relationships
-
Published lesson A Deep Dive Into Relationship CRUD with Models
-
Replied to thanks, by api resource i mean like laravel api resource https...
Ah - no, unfortunately, AdonisJS doesn't have anything that I'm aware of at present like that. Closest at the moment is the serialization options provided via Lucid, described here: https://lucid.adonisjs.com/docs/serializing-models
-
Replied to Thanks for this course. In the documentation of adonisJs 6 their...
Thanks for watching!
Yep, it sure does! You can find them in the controller section of the documentation.
router.resource('posts', PostsController).apiOnly()
Copied! -
Replied to When i call sendMail route or api i want to send the user password...
If it's sending to one inbox but not another, it's likely not a code issue but rather something invalid with how your email provider is set up. You could use a test service, like MailTrap, to verify locally that everything is okay with your code.
Once you verify that, you should be able to log in to your account with your email provider and check the logs to get more details on why it failed to send. It could be a DNS issue or even a requirements issue. For example, Yahoo and Google, have DMARC requirements.
Also, I'm not sure what your reasons are so just as a heads-up, sending user passwords via email isn't very secure as emails can be intercepted, and should their email become compromised, that password is also compromised.
-
Upvoted comment Hi, I want to send email from an email like this mail@domain...
-
Replied to Hi, I want to send email from an email like this mail@domain...
Hi Mohammad! If I'm following you correctly, you're looking to have emails sent to
[email protected]
also be received at[email protected]
, is that correct?If so, this is something you'd need to configure within your email registrar, if they support it. For example, within Zoho Mail, you would set up
[email protected]
as a group email, then add any/all[email protected]
emails you want to receive the group's emails. -
Started discussion Video Changes & Chapters
-
Published lesson Listing A Director's Movies with Relationship Existence Queries
-
Published lesson Listing and Counting a Writer's Movies
-
Published lesson Using Eager and Lazy Loading to Load A Movie's Writer and Director
-
Replied to Incase you have issues with writing to redis, head over to redis...
Thanks for sharing, Phillip!!
-
Replied to Upvote for a V6 version of this tutorial (now that the inertia...
Hey Eric! An updated version of this series is on the way, being planned now, and will begin shortly after we finish our Let's Learn AdonisJS 6 series :)
-
Published lesson Cascading and Deleting Model Relationships
-
Published lesson Defining One to Many Relationships with Lucid Models
-
Published lesson Seeding Movies with One to Many Model Factory Relationships
-
Published lesson Defining One to One Relationships Within Lucid Models
-
Published lesson Model Factory Relationships
-
Published lesson Querying Relationships and Eager Vs Lazy Loading
-
Completed lesson Generating A Unique Movie Slug With Model Hooks
-
Completed lesson Querying Recently Released and Coming Soon Movies
-
Published lesson Tapping into Model Factory States
-
Published lesson Querying Recently Released and Coming Soon Movies
-
Published lesson Generating A Unique Movie Slug With Model Hooks
-
Completed lesson Tapping into Model Factory States
-
Upvoted comment In 6, it looks like HTTPContext is a type, not an interface,...
-
Replied to In 6, it looks like HTTPContext is a type, not an interface,...
Yep, you can still extend it using the same approach, the only difference is where the type's namespace is and that it's now called just
HttpContext
instead ofHttpContextContract
. The contracts directly also no longer exists, but you can add the below anywhere you'd like, including the same file you're extending theHttpContext
within :)declare module '@adonisjs/core/http' { export interface HttpContext { organization: Organization } }
Copied! -
Replied to The adonis 6 docs talk about extending httpcontext through macros...
Yeah, the approach is still relatively the same. The only change is the type's name and namespace. In v6 you can continue just plopping additional properties directly on the HttpContext object, v5 actually had macros and getters as well :)
The types namespace is now
@adonisjs/core/http
and the name isHttpContext
, so you can extend it's type like so:declare module '@adonisjs/core/http' { export interface HttpContext { organization: Organization } }
Copied!Where you put the types doesn't matter much, you can put it in the same file you're existing the context within if you'd like or you can create a specific
types
directory off the root of your project.Here's an example of adding an
organization
property on to the HttpContext within a middleware:import type { HttpContext } from '@adonisjs/core/http' import type { NextFn } from '@adonisjs/core/types/http' import { inject } from '@adonisjs/core' import OrganizationService from '#services/organization_service' import Organization from '#models/organization' @inject() export default class InertiaMiddleware { constructor(protected organizationService: OrganizationService) {} async handle(ctx: HttpContext, next: NextFn) { const organization = await this.organizationService.active() // adding the organization onto the HttpContext ctx.organization = organization return next() } } // adding the organization onto the HttpContext interface declare module '@adonisjs/core/http' { export interface HttpContext { organization: Organization } }
Copied!Hope this helps!! :)
-
Replied to thanks for the course, really intersted to start again AdonisJS...
My Pleasure!! Thanks for watching, Ahmed! :)
-
Upvoted comment Thanks Tom for the speedy reply. I had originally tried that...
-
Replied to Thanks Tom for the speedy reply. I had originally tried that...
Anytime! Ah - yes, preloads need registered within the preloads array inside the
adonisrc.ts
file to be used; happy you were able to get your issue figured out!If you create your preload file using the Ace CLI's
node ace make:preload
command, it'll automatically register it within the preloads array for you as well. -
Replied to Ok, so the return earlier is just passing on to the next validator...
Yep, exactly!! :) Anytime!!
-
Upvoted comment Are you going to do anything with form validation? I'm trying...
-
Upvoted comment Hi Tom, really enjoying the videos. I have a question on this...
-
Replied to Hi Tom, really enjoying the videos. I have a question on this...
Hey cbernard! Thank you, I'm happy to hear that! Yeah, so the EdgeJS documentation takes a framework-agnostic approach to describing things. Within AdonisJS, the
edge
instance has already been created and you'll want to reference the already existing instance when adding your icons.Small change, but this should get things working for ya:
import edge from 'edge.js' // <-- import default export as edge instance import { edgeIconify, addCollection } from 'edge-iconify' import { icons as heroIcons } from '@iconify-json/heroicons' addCollection(heroIcons) edge.use(edgeIconify) // <-- add to that pre-existing instance
Copied! -
Replied to Are you going to do anything with form validation? I'm trying...
Yeah, validation will come with module 7, which focuses on form flow. The database modules are the last large modules, so things should start flowing quickly thereafter :)
Anything that doesn't report an error is considered valid. So, to walk through the example within Vine's documentation:
import { FieldContext } from '@vinejs/vine/types' /** * Options accepted by the unique rule */ type Options = { table: string column: string } /** * Implementation */ async function unique( value: unknown, options: Options, field: FieldContext ) { /** * 1. If the value isn't a valid string, we'll bail out here * The "string" rule will handle this particular validation * vine.string().use(unique({...})) */ if (typeof value !== 'string') { return } // 2. Otherwise, we'll continue validating uniqueness by checking the db const row = await db .select(options.column) .from(options.table) .where(options.column, value) .first() // 3. If value is NOT unique, we'll report the error if (row) { field.report( 'The {{ field }} field is not unique', 'unique', field ) } // If no error was reported by the end of the method // we'll assume everything was valid } export const uniqueRule = vine.createRule(unique)
Copied! -
Replied to Ok thanks. That might do what I want just as well.
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.
-
Replied to Hi Tom!I'm trying to inject the context into a controller, and...
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
-
Published lesson Adding A Profile Model, Migration, Factory, and Controller
-
Published lesson SQL Parameters and Injection Protection
-
Published lesson Reusable Query Statements with Model Query Scopes
-
Published lesson Querying Our Movies with the Query Builder
-
Published lesson Unmapped and Computed Model Properties
-
Published lesson Altering Tables with Migrations
-
Replied to I sent you a request on linked in. You can also get in touch...
Alrighty cool, sounds good! :)
-
Published lesson The Basics of CRUD
-
Published lesson Defining Required Data with Seeders
-
Published lesson Stubbing Fake Data with Model Factories
-
Replied to Tom, I live in OTR. Would be great to grab a beer sometime!
Oh hey, small world! Yeah, that would be cool! :)
-
Published lesson Introducing Lucid Models
-
Published lesson Defining Our Models
-
Upvoted comment thank you so much for wonderful tutorial! I have all changes...
-
Replied to thank you so much for wonderful tutorial! I have all changes...
Thank you for watching!!
I believe this is the Tailwind CSS IntelliSense extension. It should immediately start working once the extension is installed :)
-
Replied to I followed exactly same steps in VSCode for eslint and prettier...
Hey there! A few things that come to mind that may turn off or disable ESLint are:
Ensure the ESLint extension is enabled in your VSCode workspace
Ensure the ESLint extension doesn't need a VSCode reload (would say "Reload VS Code" on the extension)
Ensure you have ESLint enabled within VS Code, you can find this within VS Code's settings by typing "eslint" within the search bar.
-
Replied to The URL builder in Adonis works fine, so I generate the route...
Oh good, happy to hear you got everything working! :)
-
-
-
Replied to Thanks, I will check it out. I thought I had found the answer...
Ah - gotcha, so you're using one server to serve multiple sites? If the routes are shared by all site, then you could most likely omit the
.domain()
usage as that essentially just namespaces the routes.Anytime! Hope you were able to get things working! :)
-
Published lesson Introducing and Defining Database Migrations
-
Published lesson The Flow of Migrations
-
Replied to So this still is not working. I tried it just like that, console...
I just pushed up a working example of subdomain usage you can find at the link below. If you're linking into your subdomain, you'll want to prefix the generated url with your subdomain and include the port if you're local.
https://github.com/adocasts/lets-learn-adonisjs-6/blob/subdomain_exampleThe specific files of interest here are the route definition and the url generation inside of EdgeJS. Within the route definition I also added examples for both the URL builder and the
makeUrl
method AdonisJS provides. Theroute
method inside EdgeJS is the same as therouter.makeUrl
method, so the arguments should match . Apologies, my formatter went a little extreme with themakeUrl
usage lol.Hope this helps!! :)
-
Commented on request Splitting frontend and backend styles & scripts [AdonisJS 6]
Hey there, drummersi-3! This is something we have planned within the last module of our Let's Learn AdonisJS 6 series. That module will be specific to create an admin section.
However, to get you going before then here's some steps on how to approach this. Typically, you'd have a layout that loads in assets required for your pages. In this case, you'd have two layouts one for you base application and one for your admin section.
Your application layout, we'll call this our index layout, can use the default JavaScript and CSS files provided when creating a new AdonisJS 6 web app.
/resources/js/app.js
/resources/css/app.css
We can duplicate these and alter them as needed for our admin assets
/resources/js/admin.js
/resources/css/admin.css
We'll need to inform Vite about the new JavaScript entrypoint so that it bundles it within our
vite.config.js
fileexport default defineConfig({ plugins: [ adonisjs({ /** * Entrypoints of your application. Each entrypoint will * result in a separate bundle. */ entrypoints: [ 'resources/js/app.js', ++ 'resources/js/admin.js', ], /** * Paths to watch and reload the browser on file change */ reload: ['resources/views/**/*.edge'], }), ], // ... })
Copied!For our application pages, we can then use our index layout which will use our
app.js
andapp.css
assets.<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title> {{ title || "App Layout" }} </title> @if ($slots.meta) {{{ await $slots.meta() }}} @endif {{-- here is our asset for our index layout --}} @vite(['resources/js/app.js']) </head> <body> <div class="max-w-3xl mx-auto mt-6"> @include('partials/nav') {{{ await $slots.main() }}} </div> </body>
Copied!- resources
- views
- components
- layouts
- index.edge
Then, we can duplicate this, changing it as needed, and update our Vite asset from
app.js
toadmin.js
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title> {{ title || "Admin Layout" }} </title> @if ($slots.meta) {{{ await $slots.meta() }}} @endif {{-- here is our asset for our admin layout --}} @vite(['resources/js/admin.js']) </head> <body> <div class="max-w-3xl mx-auto mt-6"> @include('partials/nav') {{{ await $slots.main() }}} </div> </body>
Copied!- resources
- views
- components
- layouts
- admin.edge
On our application pages, we can use our app layout, like so
@layout() @slot('meta') <meta name="description" content="Our application home page" /> @endslot <h1> App page </h1> @end
Copied!- resources
- views
- pages
- home.edge
Then, for our admin pages, we can use the admin layout like so
@layout.admin() @slot('meta') <meta name="description" content="Our admin home page" /> @endslot <h1> Admin page </h1> @end
Copied!- resources
- views
- pages
- admin
- index.edge
Hope this helps!
-
Published lesson Easy SVG Icons with Edge Iconify
-
Published lesson Configuring Lucid and our Database Connection
-
Published lesson Understanding our Database Schema
-
Replied to Wow thanks. I did not get that from the documentation anywhere...
Anytime!! This site is public on GitHub and uses AdonisJS 6
https://github.com/adocasts/adocastsBeyond that, I'm not aware of any that are currently public.
-
Replied to I should probably add that the 'moms' at the start of that route...
I haven't yet worked with subdomains in AdonisJS 6, but I believe they work the same as in v5. When building the route's url using the
route()
method, the third argument accepts the domain.<a href="{{ route('moms.join', {}, { domain: 'moms.test' }) }}"> My Link </a>
Copied!The subdomain behavior is siloed, so you can have the same route defined outside the subdomain as well, and they can also be dynamic. Due to these reasons, that's why the
domain
will need specified when you're specifically looking to build a route for that domain.Hope this helps!
-
Replied to Thanks! I followed the link to the source code and it looks ...
Anytime! Yep, that should do it! 😊
-
Upvoted comment Hi Tom, can you tell me why when I inspect the request object...
-
Replied to Hi Tom, can you tell me why when I inspect the request object...
It looks like AdonisJS is using the
request.url
to parse these details out using theparse
method fromnode:url
and it looks like therequest.url
only contains the path for the request. So, it's being built without those details included and since theparsedUrl
object is just an instance ofURL
that'd be why they're still included despite being null.You can get a complete parse of the URL by doing:
router.get('/my-endpoint', (ctx) => { // passing true to completeUrl will include the query string const completeUrl = new URL(ctx.request.completeUrl(true)) return completeUrl })
Copied!Host and hostname will mostly be the same. The difference is, if the URL contains a port, host will include it and hostname will not.
REQUEST: https://test.com:3333/endpoint HOST: test.com:3333 HOSTNAME: test.com
-
Completed lesson Setting Up Tailwind CSS
-
Replied to Yup, that was it. I commented out the domain() and it works....
Oh, lol! No worries at all, I'm happy you were able to get everything figured out and working! 😄
-
Replied to Thanks again. It's weird that the routes show up when you run...
Anytime!! Yeah, I honestly don't fully understand how it could work okay in one but not the other, could be a bug or maybe just a difference between the server and console applications.
Yeah, you could absolutely hard-code the imports if you're looking for something simple, but exporting as functions seems to be the only way it's completely happy for some reason. 😊
-
Replied to So I am getting "Exception Cannot GET:" in the browser on all...
Ah - yeah I see! It works fine if you have it at the top-level but once put inside a group I see the same error as you. It almost seems like the dynamic import is hoisted or something as this is the same error you guy when AdonisJS can't find a route matching the request.
It's not pretty, and can probably be cleaned up by using macros, but exporting as functions, importing at the top-level, then calling the functions within the group does seem to get things working.
So, the base router would look like:
import app from '@adonisjs/core/services/app' import router from '@adonisjs/core/services/router' import fs from 'node:fs/promises' const files = await fs.readdir(app.startPath('routes')) const definitionFunctions: Function[] = [] for (const filename of files) { const { default: definition } = await import(app.startPath(`routes/${filename.replace('.ts', '.js')}`)) definitionFunctions.push(definition) } router.group(async () => definitionFunctions.map((fn) => fn()))
Copied!- start
- routes.ts
Then, the sub-files being imported would export as functions.
import router from '@adonisjs/core/services/router' export default () => { router.get('/test-1', () => 'working 1').as('test.1') }
Copied!- start
- routes
- test.ts
There's gotta be a cleaner way, but that's the best I can figure at the moment.
Also, yeah sorry about the formatting, I really need to add a legend/guide on what all is available. Basic Markdown is supported so you can enter a code block using three backticks. You can also open a palette by typing
/
-
Replied to I guess it didn't work. Server did not throw an error but list...
Oh rats!! I think something like this might work:
import fs from 'node:fs/promises' import app from '@adonisjs/core/services/app' import router from '@adonisjs/core/services/router' const files = await fs.readdir(app.startPath('routes')) for (const filename of files) { await import(app.startPath(`routes/${filename.replace('.ts', '.js')}`)) }
Copied!- start
- routes.ts
import router from '@adonisjs/core/services/router' router.get('/test-1', () => {}).as('test.1')
Copied!- start
- routes
- test.ts
import router from '@adonisjs/core/services/router' router.get('/test-2', () => {}).as('test.2')
Copied!- start
- routes
- another.ts
-
Replied to Yeah, I used the functions on an earlier test and it worked ...
Yeah, if you're looking for something that'll dynamically pick up new route files as you add them, that looks like a valid solution! Hopefully that'll continue to work for you! 😊
-
Replied to Yeah, absolutely, I can imagine. I'm sure you are trying to ...
Yeah lol, that's the goal it to get as much out as possible! Definitely! A lot of the principals translate pretty well between 5 and 6, so the majority of v5 content is still usable, but the module change and some API changes can cause some confusion.
-
Replied to If we were to modify this to Adonis 6, we would need to change...
Yeah, the primary thing that matters is where the route definition is run. If it's run within the group, it'll be defined within the group. For example, you could also use functions!
const postRoutes = () => { router.get('/posts').as('posts.index') router.post('/posts').as('posts.store') router.patch('/posts/:id').as('posts.update') router.delete('/posts/:id').as('posts.destroy') } router.group(() => { postRoutes() }).as('api') /* Our final post routes would be named: - api.posts.index - api.posts.store - api.posts.update - api.posts.destroy */
Copied!Since the route definitions are run inside the
api
group, they'll be defined as a portion of that group. So, in essence that's the same behavior as requiring/importing those routes shown in this lesson. -
Upvoted comment I noticed on some lessons, you have the text with screenshots...
-
Replied to I noticed on some lessons, you have the text with screenshots...
Yeah, I used to provide both written and video formats for every lesson, but ended up dropping the written portion as it's a decent time suck to produce.
For example, back when both formats were being produced I was only able to create one, maybe two, lessons per week. Now, with just the video format, we're trending at around 5-9 lessons per week.
-
Published lesson State vs Share Data Flow
-
Published lesson Share vs Global Data Flow
-
Published lesson Form Basics and CSRF Protection
-
Published lesson HTTP Method Spoofing HTML Forms
-
Upvoted comment I love how you say Laravel is "kinda similar" to Adonis. That...
-
Replied to I love how you say Laravel is "kinda similar" to Adonis. That...
Yeah lol! The core team definitely used Laravel as inspiration to shape the foundations of AdonisJS! I came from Laravel as well, JavaScript has always been my primary language so I was super happy when I learned about it 😄
-
Upvoted comment this looks really cool. I had never heard of htmx. With htmx...
-
Replied to this looks really cool. I had never heard of htmx. With htmx...
Yeah, absolutely! This site is actually built using Unpoly, which is in the same hypermedia category as HTMX. All our pages are rendered using EdgeJS despite our pages loading like a SPA and our player sticking with you throughout the site while it's playing.
-
Upvoted comment Thanks Tom. I imagine you also will be updating a lot of your...
-
Replied to Thanks Tom. I imagine you also will be updating a lot of your...
Yeah, I've got quite a bit planned including additional focus on project-based series that'll cover updated topics we covered in v5! I'll be putting 100% of my focus on this series until we wrap it up, then we'll move onward from there 😄
Also, thank you very much that's very kind!! 😊
-
Upvoted comment It would be cool to get some tutorials on using other output...
-
Replied to It would be cool to get some tutorials on using other output...
Agreed, and thank you for the suggestion! It'd be good to cover those things, and we will most certainly get to them! It won't be within this series though, as we want this series to be a good entry point for everyone coming to AdonisJS 6, and using EdgeJS is the most inclusive way to do that as it's a part of the framework.
We'll plan and add series for these into our schedule!
-
Upvoted comment Would be cool to see how to debug in VS Code!
-
Replied to Would be cool to see how to debug in VS Code!
At present, I'm not entirely sure myself how to get it working, but if/when I figure it out I'll be sure to share!! 😊
-
Published lesson Component Tags, State, and Props
-
Published lesson Use Slots To Make A Button Component
-
Published lesson Extracting A Layout Component
-
Published lesson EdgeJS Templating Basics
-
Published lesson HTML Attribute and Class Utilities
-
Published lesson Making A Reusable Movie Card Component
-
Published snippet Disable Tailwind CSS Hover States on Tap Devices
-
-
Upvoted comment thanks for this course very interesting 👍👍
-
Published lesson Quick Start Apps with Custom Starter Kits
-
Published lesson Easy Imports with NodeJS Subpath Imports
-
Replied to For v6, looks like serializeOnly and serializeExcept have been...
Yeah, I need to add a way to tag these series to specific versions! Though the process is still the same, the syntax for this series is using EdgeJS & AdonisJS 5. If you're curious, you can find the full list of differences between EdgeJS 5 vs 6 here:
-
Upvoted discussion Welcome To The Feed!
-
Published lesson Environment Variables and their Validation
-
Published lesson Improved Caching with Redis
-
Published lesson Deleting Items and Flushing our Redis Cache
-
Published snippet Simple AdonisJS 6 Layout Component
-
Completed lesson Environment Variables and their Validation
-
Upvoted comment For mystical reasons, I missed that part. Sorry :DBy the way...
-
Replied to For mystical reasons, I missed that part. Sorry :DBy the way...
No worries at all, Jean!! :D
Thank you, I really appreciate that!!
-
Upvoted comment Hi,When I save routes.ts file, imports remain identical. Do ...
-
Replied to Hi,When I save routes.ts file, imports remain identical. Do ...
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! -
Started discussion Welcome To The Feed!
-
Published blog post What's New in Adocasts V3
-
Replied to Really happy to see you do the model and migration stuff separately...
Thank you, pat_toast!! Yeah, for sure, it can definitely be tricky to grasp where migrations stop and models start and what the separation of concern is between them.
-
Completed lesson Singleton Services and the Idea of Caching
-
Completed lesson Defining A Structure for our Movie using Models
-
Published lesson Cleaning Up Routes with Controllers
-
Published lesson Defining A Structure for our Movie using Models
-
Published lesson Singleton Services and the Idea of Caching
-
Completed lesson Cleaning Up Routes with Controllers
-
Completed lesson Extracting Reusable Code with Services
-
Published lesson Reading and Supporting Markdown Content
-
Published lesson Listing Movies from their Markdown Files
-
Published lesson Extracting Reusable Code with Services
-
Published news AdonisJS 6 Has Landed!
-
-
Upvoted comment Great Lesson!
-
Completed lesson What We'll Need Before We Begin
-
Completed lesson Introducing AdonisJS
-
Published lesson Introducing AdonisJS
-
Published lesson What We'll Need Before We Begin
-
Published lesson Creating A New AdonisJS 6 Project
-
Published lesson Project Structure
-
Published lesson VS Code Extensions and Configuration
-
Published lesson Routes and How To Create Them
-
Published lesson Rendering a View for a Route
-
Published lesson Linking Between Routes
-
Published lesson Loading A Movie Using Route Parameters
-
Published lesson Validating Route Parameters
-
Published lesson Vite and Our Assets
-
Published lesson Setting Up Tailwind CSS
-
Completed lesson VS Code Extensions and Configuration
-
Published lesson Form HTTP Method Components
-
Published lesson Form Component Method Spoofing
-
Published lesson Form Utility Component
-
Anniversary Thanks for being an Acocasts member for 3 years
-
Published lesson Bordered, Active Bordered, and Plain Variants
-
Published lesson Identifying Accordion Items
-
Published lesson Starting Our Accordion
-
Published news AdonisJS 6 Release Date Announced
-
Published lesson Model vs Database Query Builder