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
- 64
- Comments Contributed
- 403
- Hours Watched
- 7.28
Recent Activity
Here's what @tomgobich has been up to this past year
-
Upvoted comment Hi Tom, the project I'm working on to learn how to use Adonis...
-
Replied to Hi Tom, the project I'm working on to learn how to use Adonis...
Hi n2zb! You can preload pivot table data two ways.
First option is to define them directly in the relationship definition, as shown in this lesson at
. This will always include them when the relationship is preloaded. So, this is a great option is you'll frequently need that pivot data.export default class Movie extends BaseModel { // ... @manyToMany(() => Cineast, { pivotTable: 'cast_movies', pivotTimestamps: true, pivotColumns: ['character_name', 'sort_order'], // 👈 }) declare castMembers: ManyToMany<typeof Cineast> // ... }
Copied!The second option is to add them on a per-query basis, as needed.
const crew = await movie .related('crewMembers') .query() .pivotColumns(['title', 'sort_order']) // 👈 .orderBy('pivot_sort_order')
Copied!This will then add those pivot columns into the
$extras
object of the preloaded relationship.const crewMember = { // ... '$extras': { pivot_title: 'Art Director', // 👈 pivot_sort_order: 0, // 👈 pivot_movie_id: 417, pivot_cineast_id: 40, pivot_created_at: DateTime { ts: 2024-03-30T11:59:58.911+00:00, zone: UTC, locale: en-US }, pivot_updated_at: DateTime { ts: 2024-03-30T11:59:58.911+00:00, zone: UTC, locale: en-US } }, }
Note that the column names are prefixed with
pivot_
and that the ids of the relationship are always included. Also note, if you're serializing your models, you'll want to turn on extras serialization via the below on the model, and they'll be serialized asmeta
.export default class Cineast extends BaseModel { serializeExtras = true // ... }
Copied!We get into all of this in various later lessons in this series as well, if you'd like a deeper dive!
-
Upvoted comment //cache service export class CacheService { async has(...keys...
-
Upvoted comment //redis controller export default class RedisController { ...
-
Replied to After testing with these two route methods, there were no changes...
Hi codthing! Everything you've shared looks right. If you're viewing your Redis database with a GUI application, those often don't reflect live data and require refreshing in order for them to reflect changes made to the database contents. Also, make sure you're viewing the Redis database actually being used by your application! This can be found within
config/redis.ts
const redisConfig = defineConfig({ connection: 'main', connections: { main: { host: env.get('REDIS_HOST'), port: env.get('REDIS_PORT'), password: env.get('REDIS_PASSWORD', ''), db: 1, // 👈 indicates I'm using Redis DB1, DB0 is typically the default keyPrefix: '', retryStrategy(times) { return times > 10 ? null : times * 50 }, }, }, })
Copied!- config
- redis.ts
You might also do a sanity check, just to ensure your controller methods are actually being hit correctly, by adding a console log to each.
//redis controller export default class RedisController { public async destroy({ response, params }: HttpContext) { console.log('destroy', params.slug) await cache.delete(params.slug) return response.redirect().back() } public async flush({ response }: HttpContext) { console.log('flushing redis') await cache.flushDb() return response.redirect().back() } }
Copied!If neither of those help, please feel free to share a link to your repository and I can take a deeper look to see if anything stands out!
-
Completed lesson Deleting Items and Flushing our Redis Cache
-
Upvoted comment Thanks Tom. Thats what i am seeking for. I will find way to ...
-
Replied to Thanks Tom. Thats what i am seeking for. I will find way to ...
Anytime! Okay cool, yeah updating the logger destination and logging the queries, as shown in the last two code blocks in my comment above, should get that working for ya!
-
Replied to How can i log the query to check if i am writing query correct...
Hi Rajeeb!
Console logging a call to
toSQL()
as you have should work, you should be seeing something like the below in your console.However, there is a nicer way to do this, using pretty printed query logging. Within your
config/database.ts
, setprettyPrintDebugQueries: true
.const dbConfig = defineConfig({ prettyPrintDebugQueries: true, // 👈 connection: 'postgres', connections: { postgres: { client: 'pg', connection: { host: env.get('DB_HOST'), port: env.get('DB_PORT'), user: env.get('DB_USER'), password: env.get('DB_PASSWORD'), database: env.get('DB_DATABASE'), }, migrations: { naturalSort: true, paths: ['database/migrations'], }, }, }, })
Copied!- config
- database.ts
Then, you can either globally enable query logging, by adding a
debug: true
to your driver object.const dbConfig = defineConfig({ prettyPrintDebugQueries: true, connection: 'postgres', connections: { postgres: { debug: true, // 👈 client: 'pg', connection: { host: env.get('DB_HOST'), port: env.get('DB_PORT'), user: env.get('DB_USER'), password: env.get('DB_PASSWORD'), database: env.get('DB_DATABASE'), }, migrations: { naturalSort: true, paths: ['database/migrations'], }, }, }, })
Copied!- config
- database.ts
Or, you can enable this this on a per-query basis by adding
debug(true)
to the query.await Collection.query().where('slug', params.slug).debug(true).firstOrFail()
Copied!With that set, you should see something like below in your console
Now, unfortunately, this pretty print can't be logged to a file. You can still log the query to a file though. Within your
config/logger
, set the non-production logger target to a file at the destination you'd like. If that's a file in storage, it'd look something like:const loggerConfig = defineConfig({ default: 'app', loggers: { app: { enabled: true, name: env.get('APP_NAME'), level: env.get('LOG_LEVEL'), transport: { targets: targets() // 👇 .pushIf(!app.inProduction, targets.file({ destination: './storage/local.log' })) .pushIf(app.inProduction, targets.file({ destination: 1 })) .toArray(), }, }, }, })
Copied!- config
- logger.ts
Then, add a global listener for the query within a preload.
node ace make:preload events
import emitter from '@adonisjs/core/services/emitter' import logger from '@adonisjs/core/services/logger' emitter.on('db:query', function (query) { logger.debug(query) })
Copied!- start
- events.ts
With this all your logs, including queries, will be written to the file at the location you've specified! Hope this helps!! 😊
-
Upvoted comment Having worked exclusively with Express and EJS on the backend...
-
Replied to Having worked exclusively with Express and EJS on the backend...
That's awesome to hear, Vladimir! I'm really happy to hear you're finding the lessons early to follow and enjoying them, thank you for sharing!
-
Replied to Thank you so much!! I tried many tags without success but tbody...
Anytime! Awesome, I'm happy to hear that got things working for you, memsbdm!!
-
Published lesson How to Install & Configure TailwindCSS 4 in AdonisJS 6 using Vite
-
Upvoted discussion Vue Sortable
-
Upvoted comment By the way I am trying to use inside a and it says Hydration...
-
Replied to By the way I am trying to use inside a and it says Hydration...
Hey memsbdm! Yeah, the deployed app has been expanded beyond the scope of the Building with AdonisJS & Inertia series. It's what I use to plan lessons and series, and also feeds the data shown on our schedule page. So, I've added some things since. Apologies if that caused any confusion!
I did have the repository private, but just made it public. You can find the code for the live PlotMyCourse app here.
The
<Sortable>
component accepts a tag prop, and I have that set astbody
so that it plays well structurally with the table itself. I think that might be all you're missing!<Table> <TableHeader> <TableRow> <!-- ... --> </TableRow> </TableHeader> <Sortable v-if="courses?.length" v-model="courses" item-key="id" handle=".handle" tag="tbody" <!-- 👈 --> class="[&_tr:last-child]:border-0" @end="onOrderUpdate" > <template #item="{ element: course }"> <TableRow class="group"> <!-- ... --> </TableRow> </template> </Sortable> </Table>
Copied! -
Upvoted comment perfect, thanks :)
-
-
Upvoted comment Worked fine. Thank you!
-
-
Upvoted comment In the file inertia/composables/resource_action.ts, this line...
-
Replied to In the file inertia/composables/resource_action.ts, this line...
Hi memsbdm!
It looks like this was a tiny change that occurred between InertiaJS v1 and v2! In this lesson, I'm still working with Inertia v1, which is why everything works without issue for me. We upgrade to InertiaJS v2 in module 13, though this is one change I missed!
Here's what the
FormDataType
looks like in InertiaJS v1:And then here is what it is now in InertiaJS v2:
So, your solution is perfect for v2! You could probably try and use
`Record<string, FormDataConvertible>
if you'd prefer to be as accurate as possible! Though I don't believe what you have should cause any issues down the road! Thank you for sharing! 😊 -
Upvoted comment Thank you, I had missed that information in the documentation...
-
Replied to Thank you, I had missed that information in the documentation...
Anytime and no worries at all! That's completely understandable!! 😊
-
Replied to Hi, thanks for creating the series. I'm used to creating folders...
Hi Nidomiro!
Yep, that's absolutely a valid approach with AdonisJS! I haven't done it myself, but you can check out Romain Lanz's website which uses this setup. He is one of the AdonisJS Core Members.
You'd need to move some files around to your liking manually, then update the
imports
within yourpackage.json
to match your feature names (if you want to use import aliases) rather than having one for controllers, models, etc. -
Upvoted discussion building-with-adonisjs-and-inertia repository setup error on local machine.
-
Replied to discussion building-with-adonisjs-and-inertia repository setup error on local machine.
Hi Thiago!
Sorry you're having issues! I think I found the culprit. It looks like TailwindCSS was having difficulty picking up the default configuration to use. Explicitly defining the config seems to have fixed the issue - at least on my end, but I couldn't get the error to consistently happen.
Please let me know if this remedied the issue for you!
Thanks and sorry again you're having issues!
-
Published lesson Dump & Die Debugging in AdonisJS 6
-
Upvoted comment Awesome! Thank you!
-
-
Replied to Hello, In this video, you are using a transaction with db.transaction...
Hi Tigerwolf!
Lucid cascades the transaction to relationship operations for us! So, when we attach the transaction to the operation that creates the organization, the transaction reference will be kept in the organization instance.
return db.transaction(async (trx) => { const organization = await Organization.create(data, { client: trx }) organization.$trx // 👈 holds the transaction // ... })
Copied!This transaction reference is then automatically used when we perform subsequent operations with this organization, which includes related operations.
return db.transaction(async (trx) => { const organization = await Organization.create(data, { client: trx }) // 👇 passes $trx on the organization along with the attach db operation return organization.related('users').attach({ [user.id]: { role_id: Roles.ADMIN, }, }) // ... })
Copied!The managed transaction, then, wraps the callback within a try/catch block. The transaction is committed if the callback completes and no errors are thrown. Otherwise, if an error is caught, the transaction is rolled back. Essentially doing the below for us in a nicer syntax.
const trx = await db.transaction() try { const organization = await Organization.create(data, { client: trx }) // 👇 passes $trx on the organization along with the attach db operation return organization.related('users').attach({ [user.id]: { role_id: Roles.ADMIN, }, }) // ... await trx.commit() } catch (error) { await trx.rollback() }
Copied!So, as long as we don't eat the errors thrown by the operations we're performing, the managed transaction will catch it and roll back our transaction. This is regardless of how we structure our code within the managed transaction, including splitting it into additional methods.
So, to answer your question, If an issue occurs in
assignAdmin
orcreateDefaults
the managed transaction will catch this and roll back our transaction for us! You can see this for yourself by trying to assign the user arole_id
that doesn't exist. Since we're using a foreign key constraint here, attempting to do so will throw an error.static async #assignAdmin(organization: Organization, user: User) { return organization.related('users').attach({ [user.id]: { role_id: 45, //Roles.ADMIN, // 👈 replace admin with some non-existant id }, }) }
Copied! -
Upvoted comment Thank you very much! Your answer solved my problem.
-
Replied to Thank you very much! Your answer solved my problem.
Anytime! Awesome, glad to hear you’re all set Oliver!
-
Upvoted discussion Passing parameters to the router line
-
Replied to discussion Passing parameters to the router line
Hi Oliver!
Partial route parameters, like
/fly-to-:city
aren't supported by the AdonisJS Router at present. However, you can use a vague route parameter in conjunction with a route param validator to make this work.router.get('/:flyToCity', [Controller, 'handle']).where('flyToCity', /^fly-to-/)
Copied!The
where
here is applying a validator to the route parameter, stating that the parameter should start with "fly-to-" in order for the route to match the definition.We have a lesson where we cover this using user profiles in our Let's Learn AdonisJS 6 series if you'd like to learn more!
-
Upvoted comment thank you so much, I've been able to set it up. Thank you so...
-
Replied to thank you so much, I've been able to set it up. Thank you so...
Awesome, I'm really happy to hear you're all set up now! Thanks a bunch, Ikeh!! 😊
-
Replied to No worries at all! It's been a long while since I've used MySQL...
Alrighty, so I took a look into it. I was also getting the error with MySQL, and it was indeed a code issue! I accidentally missed binding the organization to the transaction, which caused the decrement to run outside the confinement of the transaction - hence the lock block.
Below is the updated code that fixes this issue! Terribly sorry about the miss here! I'll get a note about this added into the lesson.
export default class DestroyLesson { static async handle({ organization, id }: Params) { const lesson = await organization.related('lessons').query().where({ id }).firstOrFail() await db.transaction(async (trx) => { lesson.useTransaction(trx) organization.useTransaction(trx) // 👈 await lesson.delete() await organization .related('lessons') .query() .where('moduleId', lesson.moduleId) .where('order', '>', lesson.order) .decrement('order') }) return lesson } }
Copied! -
Upvoted discussion Not able to share flash messages to frontend with inertia
-
Replied to discussion Not able to share flash messages to frontend with inertia
Hi Deniz!
I think you've got a small typo. You're flashing to
notification
in your controller, but sharingnotifications
. You'll want those two to match .Also, make sure you've got the flash message key
notification
ornotifications
(whichever you pick) defined as a prop on your page/layout. Otherwise Vue, for example, won't ingest it and will treat it as an attribute and not a prop. -
Upvoted comment Thanks! I found it. A typo… was missing the in organization....
-
Replied to Thanks! I found it. A typo… was missing the in organization....
Anytime! Awesome, I'm glad to hear you were able to get it fixed up! As a morning person, I can concur! 😄
-
Replied to Sorry for all the questions. I am getting a lock timeout error...
No worries at all! It's been a long while since I've used MySQL, so my platform-specific knowledge here will be limited. But, if it works fine without the decrement then I'd imagine you most likely have a stuck lock or transaction on one of your lessons. Something not directly related to the decrement, but rather blocking the decrement from succeeding.
This may or may not be code-related. It could be a lock added by a client application or CLI using your MySQL table. It could also be another transaction touching one of the lesson rows that was never committed or rolled back. Here is a StackOverflow thread that discussed some query options to dig into what might be locking things up.
I can try and get MySQL set up and test it out this evening after work to ensure it isn't a code issue specific to MySQL.
-
Replied to finding it difficult to install Redis on Windows. any alternative...
Hi Ikeh! Redis is used for a very brief period in this series. I believe 2.14 and 2.15 are the only two lessons we use it within, just to show how to work with it. It is completely skippable if you don't feel like setting it up on your machine!
Alternatively, you can use Redis Cloud. They offer a small free tier if you click "Try for Free" and then select the "Essentials" plan option during sign-up, as shown below.
Once you've created your database, you can click on "connect" and then expand the "Redis Client" section. Here you can copy/paste the username, password, host, and port into your
.env
file of your project.Note: you have to click the "copy" button to get the actual password
-
Upvoted comment Yeah I finally tried to recreate the GetOrganizationAbilities...
-
Replied to Yeah I finally tried to recreate the GetOrganizationAbilities...
That's awesome! I'm happy to hear you're making progress with it! I wouldn't worry too much about doing things the best or cleanest way, especially initially! So long as it works for what you need in a way you understand, that's all that matters to keep progress going. You can always refactor down the road if need be. 😊
-
Replied to Thank you! Yes, I found from another forum that I was not using...
Awesome! I'm glad to hear you were able to get it figured out! 😊
-
Upvoted discussion shared data in inertia config is not coming into vue props
-
Replied to discussion shared data in inertia config is not coming into vue props
Hi Ryan!
The authenticated user isn't populated until you tell AdonisJS to populate it. This can be done using the
auth
middleware,silent_auth
middleware, or manually callingauth.check()
as needed. You're most likely missing one of those.Note that you also need to specify a serialized type for your Lucid Models using either casting or DTOs.
-
Upvoted comment I went with the row level with model hooks. Works well, I had...
-
Replied to I went with the row level with model hooks. Works well, I had...
Yeah, you'll want the
HttpContext
here to be optional. Then, when you don't have anHttpContext
, you'll want to signal to yourself that your permissions haven't been populated; leaving itnull
there could be a way to do that.@afterFind() static async attachPermissions(workspace: Workspace) { const ctx = HttpContext.get() if (!ctx) return // ... }
Copied!If the
HttpContext
isn't optional then your model would also become unusable within commands, jobs, events, and all that fun stuff. In most cases, you shouldn't need permissions on those types of requests. On the off chance you do, that's where you'd want to extract the contents of the hook into a model method, service, or action so you can also use the same logic on an as-needed basis. -
Replied to The line statusId: props.organization.statuses.at(0)?.id works...
Hi Aaron!
It sounds like your
organization
is undefined within theSortableLessons
component. If it works fine withinSortableModules
then it should be populated on the page okay. Make sure you've got it within the props being passed intoSortableLessons
!<SortableLessons v-model="modules[index]" :organization="organization" <!-- 👈 --> :course="course" />
Copied!If it's there, then make sure it is within
SortableLessons
props.const props = defineProps<{ organization: Organization // 👈 course: CourseDto modelValue: ModuleDto }>()
Copied!I'd reckon it's one of those two, but if it is in both of those spots, I'd recommend using the Vue DevTools to inspect the flow of this prop to see where it might be getting lost.
Hope this helps!!
-
Upvoted comment just saw this now after my complaint. thank you
-
Replied to just saw this now after my complaint. thank you
Oh shoot - I recorded these updates and got the Inertia one updated but missed this one. I'll get this updated after work!
-
Upvoted comment Thank you! 🎊🎉🥳
-
-
Replied to Hi again Tom ! Sorry I ask a lot of questions 😅I was wondering...
Hi Emilien!
No worries at all!! 😊
Yeah, things spiral here quickly depending on your approach. It's a big enough of a topic to be a series of its own, but I'll try to give an overview below.
Access Determination
So, the first thing you need to do is figure out how the more granular access will be determined. Will only the creator have permission to edit, update, and delete? Or, maybe the creator can invite others to edit, update, and delete? Essentially, how are the different roles going to be determined at those different levels?
Creator Only
If it's just the creator, then that's much easier. You'd want to capture the
userId
of the user who created the resource. For example, adding aownerId
onto theCourse
model. Then, you'd handle the logic very similarly to how we allow org members to remove themselves throughout the next two lessons (12.1 & 12.2). By giving priority to allow the action if theownerId
matches the authenticated user's id. When that's the case, you'd want to ignore whatever the role's permission says and allow the action.Resource Specific Invitations
If you want to allow the creator to invite/share with others to collaborate, you'll need a table to track that. This table could look many different ways.
It could be a single table per resource, ex:
course_user
consisting ofcourse_id
,user_id
, androle_id
if there are different levels and not an on/off.A polymorphic table, ex:
permission_user
consisting of something likeentity
,entity_id
,user_id
, and againrole_id
if needed. Here,entity
would be the table/model andentity_id
would be the row's id for that table/model. Now, Lucid doesn't support polymorphic relationships but that doesn't mean you couldn't specifically query for what you need. The downside is, that you can't use foreign keys for theentity_id
since it'll be dynamicA single table with a nullable column per resource, ex:
permission_user
consisting of something likecourse_id
,another_id
,user_id
, and againrole_id
if needed. This is similar to the polymorphic relationship, but you have a specific column per resource needed. Plus here, is you can keep foreign keys. Downside here, is it can be a lot of columns if a lot of resources need to use it.
There are many different ways authorization can look beyond these two, but determining what that looks like is the first step.
Populating Permissions
When it comes to populating these permissions, again, it's going to depend on just how granular you need to go. For us in this series, things were simple so our approach was simple. The approach covered here was meant mostly for a single set of roles that then trickle down as needed.
If you're going to be listing courses and each of those courses in the list is going to have varying permissions based on invites/shares, then you might be better off moving the
can
approach to the row level. For example, allowing something likecourse.can.edit
to determine if the authenticated user can edit the course. This could be done using something like model hooks.You could also always reach for a package to help achieve your implementation.
That was a lot, sorry! Hopefully, that helps at least little though.
-
Replied to Thanks for your effort. I will find out issue and let you know...
Sorry I couldn't be of more help, Rajeeb! I hope you're able to find a solution. You might have luck reaching out on the AdonisJS Discussions or Discord.
-
Upvoted comment Unbelievable !!! :-O I wanted to sincerely thank you for your...
-
Replied to Unbelievable !!! :-O I wanted to sincerely thank you for your...
My pleasure, tigerwolf!! Happy to be able to help! 😊
-
Replied to Hello,First of all, thank you for the high-quality content you...
Hello, tigerwolf!
Sorry about the inconvenience!! Thankfully, the video host we're using for these videos allows specifying custom CSS within the player's iframe, and I was able to get the captions pushed up when the video toolbar is shown. I also hid the heatmap, which seems to be mostly what was going over the subtitles.
We're in the process of switching from an actual video host to Cloudflare R2 with a hand-picked player, so hopefully we'll have more improvements in this area down the road!
Thank you very much for the feedback!!
-
Upvoted comment Hi, thank you for this content. Unfortunately , subtitles are...
-
Replied to Hi, thank you for this content. Unfortunately , subtitles are...
Terribly sorry about that, tigerwolf! The subtitles for this lesson have now been corrected. Thank you very much for the heads up!
-
Upvoted discussion Recently from today i am getting error on node package on adonisjs
-
Replied to discussion Recently from today i am getting error on node package on adonisjs
Hi Rajeeb!
Do you have
ts-node-maintained
installed? This package is a version the AdonisJS Core team support that's a maintained fork ofts-node
.Please make sure that this package is installed and that you're attempting to run your Ace CLI command from the correct directory. If in development, that'll be the root of your project. If you're attempting to run a built application, that'll be in the
build
directory. I'm unfamiliar with Docker, but that may be a source of the issue as well.As a sanity check, I just pulled down a new installation of the AdonisJS Inertia starter kit and it installed and booted successfully.
-
Replied to Hi Tom,I actually managed to make it work by reporting an custom...
Hi emilien!! I think your linked repo is private, but I'm happy to hear you were able to find a solution! I just took a look into it and the target was
email.database.unique
where email is the validator field's name.The below, within
resources/lang/en/validator.json
, worked for me!{ "shared": { "messages": { "email.database.unique": "This is from the translation" } } }
Copied!You can find the logic used to find a translated message within the
I18nmessagesProvider
. Also, my initial thinking was incorrect. This is actually used immediately by the validator, not inside the middleware! -
Published lesson Defer Loading Props in InertiaJS 2
-
Published lesson Deferring A Prop Load Until it is Visible in InertiaJS 2
-
Published lesson Super Easy Infinite Scroll in InertiaJS 2 with Prop Merging
-
Upvoted comment Hi Tom, I was wondering how we can set custom error messages...
-
Replied to Hi Tom, I was wondering how we can set custom error messages...
Hi emilien!
I actually haven't used the i18n AdonisJS package at all yet, probably should familiarize myself with it though haha.
The field target should just be
email.unique
, I believe. If you're trying to use the Detect User Locale middleware to automatically do this, it might be possible that the Inertia middleware is committing it's session store before the Detect User Locale middleware is able to apply the translations. Playing with the middleware order there, having inertia before the Detect User Locale insidestart/kernel.ts
, might help.You could also try passing it directly into the
validate
method, as shown here.When I have some free time, I'll play around with it a bit to see if I can get it figured out!
-
Upvoted comment Hi there! I’m currently using adocasts.com and I’ve actually...
-
Replied to Hi there! I’m currently using adocasts.com and I’ve actually...
Thanks so much for sharing, cortesbr!! I believe this is either a browser or extension issue. After closely inspecting, I think the page is actually still there & active, the browser just isn't showing it to you for some reason.
You do click several places, but at
you click somewhere that actually has an anchor link. At the site comes back and briefly shows the linked anchor that was clicked at is "Allowing Admins to Update Movies and Clear Values". Moments later the page for that clicked lesson loads.I've been trying for the past half hour or so to replicate this on my Windows machine using Edge, and haven't had any luck. Browsers, Edge included, have a number of battery/resource saving features like putting inactive tabs to sleep. This could be one cause - though I tested several Edge settings and couldn't replicate. Extensions are also common sources of issues.
I'll keep an eye out and try replicating further, but in the meantime I'd recommend giving it a go in a private/incognito window to see if that fixes the issue you're having. If I had to place a wager, I'd guess it's an extension.
-
Replied to Hi everyone, you can also add these css classes to `DropdownMenuContent...
Thanks for sharing, jals!!
-
Upvoted comment Thanks. Just a note, I tried npx shadcn-vue@radix add table ...
-
Replied to Thanks. Just a note, I tried npx shadcn-vue@radix add table ...
Looks like
npx shadcn-vue@radix
is targeting 0.11.4, just as an alias, so those should behave the same. It's possible there was a network issue - their add commands send API requests out to the actual ShadCN site to get configurations, like colors. Based on this issue, you might also have an invalidbaseColor
in yourcomponents.json
.Here's my attempt to run it and you can see it prompted me to install v0.11.4
The unsupported engine warning you got is stating that the package
undici
requires NodeJS v20.18.1 or higher, but you have v20.11.0 installed. So, that'll go away if you update your NodeJS version, not entirely sure what makes use ofundici
. -
Upvoted comment It looks like shadcn updated recently to use Reka instead of...
-
Replied to It looks like shadcn updated recently to use Reka instead of...
Yep! I'm working on updating lesson 1.4 of this series this weekend just for this, though in the updated lesson, I'll recommend sticking with Radix for the time being. Getting their Reka update working is too much of a change, and there may be several inner-component changes as well that would confuse those following this series. You can still access the documentation for the Radix version at radix.chadcn-vue.com. You can also target it via their CLI via:
npx shadcn-vue@radix <command>
I've tried getting their Reka updating working myself, and tried re-initializing - neither work without compromise. They dropped support for specifying a
tsconfig.json
location and will instead only check the project root for atsconfig.json
,tsconfig.web.json
, ortsconfig.app.json
file in that order. They have a preflight check on their CLI commands that will fail the command if the foundtsconfig
is missing an alias definition, which is also why it states thecomponents.json
file is incorrect despite it matching their spec.So the only possible way to get their Reka UI update working would be to rename the AdonisJS
tsconfig.json
file in the root of the project to something else, as Shadcn-Vue's preflight command check will only ever find and use this tsconfig file due to its name and priority in their search.You'd then need to move your Inertia
tsconfig.json
file out into the project root.I haven't tried, but you may have luck getting things working if you move all the Shadcn-Vue stuff into the
inertia
folder. -
Upvoted discussion Delayed Page Rendering on Tab Focus
-
Replied to discussion Delayed Page Rendering on Tab Focus
Hi cortesbr! I'm not sure I'm fully following. Would you mind providing a little more context, please?
For example, are you experiencing this behavior:
On the actual Adocasts site here at adocasts.com?
On a cloned Adocasts site locally on your machine?
As part of the code from the Let's Learn AdonisJS 6 series in the linked lesson?
Additionally, when you say the page initially appears blank - is this completely blank as in nothing at all shows, or is it just a certain section of the page? If it is just a certain section, which section would that be?
Thanks in advance!!
-
Replied to Thanks for your answer, I was able to register some users properly...
Anytime! Awesome, great to hear things are working for you now!
No worries at all, always happy to help where I can! 😊 -
Upvoted comment Thanks for guidance. Now it worked.
-
-
Published lesson Upgrading to Inertia 2
-
Published lesson Polling for Changes in InertiaJS 2
-
Published lesson Prefetching Page to Boost Load Times in InertiaJS 2
-
Completed lesson Upgrading to Inertia 2
-
Upvoted comment i am stuck on initializing shadcn-vue /var/www $ npx shadcn-...
-
Replied to i am stuck on initializing shadcn-vue /var/www $ npx shadcn-...
Hi Rajeeb! Apologies about that, it looks like Shadcn-Vue changed their init command to require more manual work to get things set up. Please try initializing using the latest 0 version with:
npx shadcn-vue@0 init
I'll have to compare to see what/why they changed it, and I will update this lesson accordingly!
—
Edit: Okay, so it looks like in v1 they've switched the default core library from using Radix-Vue to Reka-UI. Using the below to initialize your Shadcn-Vue installation is now the recommended command to continue using Radix, which is what this series uses.
npx shadcn-vue@radix init
With this command, this installation should match that covered in this lesson. I'll get this lesson updated accordingly.
-
Upvoted comment Hello, Tom,I see in the LucidModel interface that there is a...
-
Replied to Hello, Tom,I see in the LucidModel interface that there is a...
Hi n2zb!
I have never tried changing the
table
property at runtime like you're asking about. I imagine it would work, but even if it does, it would be susceptible to accidentally entering data into the wrong table.Instead, I'd recommend defining those shared columns & relationships via a Model Mixin and then composing the mixin into a separate model per table. I thought I had a free lesson on this, but it looks like there is currently only this one from the Building with AdonisJS & Inertia series. I'll have to make a free one on this as well, as it's quite handy.
In the meantime, you can get the gist of it via the code from this series.
With Organization Model Mixin that defines an
organizationId
and relationship
-
Replied to Hello, I did few steps till 05:10 and got an error ( Unexpected...
I'm not getting that exact error, but I see a couple of issues that'll cause things not to work correctly.
First, for your form errors, you want to use
$messages
not$message
<label> <span> Full Name </span> <input type="text" name="fullName" value="{{ old('fullName') || '' }}" /> @inputError('fullName') {{ $messages.join(', ') }} {{-- 👈 use $messages --}} @end </label>
Second, your full name input has the name
fullName
with a capitalN
while your validator hasfullname
, these need to match . With those mismatched and the field required by your validator, you'll always get a validation error on submission.export const registerValidator = vine.compile( vine.object({ fullName: vine.string().maxLength(100), // 👈 use fullName email: vine.string().email().normalizeEmail(), password: vine.string().minLength(8), }) )
Copied!Hope this helps! If, after these fixes, you're still getting the state error, please let me know which page and operation you're performing that's causing that error.
-
Upvoted comment I've tried to make a flowchart of the middleware and how it ...
-
Replied to I've tried to make a flowchart of the middleware and how it ...
Oh nice! Yeah, this looks spot on, nicely done emilien! Thank you, I really appreciate that offer! I'm happy to hear that going through this was fruitful!! 😃
-
Upvoted comment Hi. Anyone having issues with npx tailwindcss init -p, just...
-
Replied to Hi. Anyone having issues with npx tailwindcss init -p, just...
Yeah, I should probably add a note in this video for that.
npx tailwindcss init -p
is a TailwindCSS 3 command. TailwindCSS 4's default installation doesn't include atailwind.config.js
file anymore and comes with its own Vite plugin for simplified configuration. -
Upvoted comment Is AdonisJS set up to handle soft deletes? If you didn't want...
-
Replied to Is AdonisJS set up to handle soft deletes? If you didn't want...
Hi Aaron! Unfortunately, not out of the box. Looks like there is a community package to add support for it, though. An alternative approach I've seen discussed is to have a
soft_deletes
table that contains a serialized version of the deleted row with additional columns specifying its original table & id. You'd then delete the record, serialize it, then add it to thesoft_deletes
table so that you can still grab it and restore it if needed. I've never attempted this approach, but it does sound interesting, so I thought I'd share. Not sure how this would handle foreign keys pointing to the deleted id though.Yes, sorry about that, those should be optional props!
-
Replied to I think that prettier-ignore-start only works for markdown files...
Hi Nathan! Huh - yeah it sure does. 🤔
Oh snap, looks like I disabled Prettier formatting on save for TypeScript files at some point. So, Prettier just isn't running for me unless I paste or explicitly format, sorry about that! Your solution does look to be the recommended approach. I'm going to have to re-enable Prettier and add that myself. Thank you for sharing!!
-
Replied to Great! I see you're already working on this for the next lesson...
Yeah, we'll add a fix for this at the end of lesson 13.0! 😊 Thanks again, jals!!
-
Upvoted comment Of course this helps, could have spent a lot of time on this...
-
Replied to Of course this helps, could have spent a lot of time on this...
Awesome, glad to hear that helped clear things up! Ah, please don't feel dumb about that, it is just a small oversight. We've all done a lot worse!! 😊
That sounds like a great plan! Having a document/diagram of how things work would definitely help. That's something I'd like to make a more concerted effort to do in-lesson.
Anytime emilien!! Always happy to help where I can! 😊
-
Upvoted comment You should receive organizationId in the route and validate ...
-
Replied to You should receive organizationId in the route and validate ...
Hi jals! That's a great point and a use-case that slipped my mind, thanks for catching that!
Adding the
organizationId
into the route parameters is definitely a great option. If you add theorganizationId
into the URL though, I might prefer to do that across the board and drop theactive_organization
cookie altogether. That way you only have one source of truth on which organization is being worked with. Minimizing potential confusion down the road.You could, alternatively, use a Broadcast Channel to communicate across the tabs to notify the others when the
organizationId
has changed. You could then display a modal warning the user, offering to re-up their active org. You could also add an event when the tab is focused to update, set, or check the activeorganizationId
. Something like the below would probably suffice.onMounted(() => window.addEventListener('focus', onFocus)) onUnmounted(() => window.removeEventListener('focus', onFocus)) function onFocus() { router.get(`/organizations/${props.organization.id}`) }
Copied! -
Upvoted comment Hi Tom, thanks for your reply. After reading my first comment...
-
Replied to Hi Tom, thanks for your reply. After reading my first comment...
Alrighty, I think I see what's going on! Where your project differs is that your
/workspaces/:id
route merely renders a page. In our project, and even in yourmain
branch, this instead sets the active org/workspace in our PlotMyCourse & on your main branch, then redirects back to the dashboard.Where I think the confusion comes in is the
/:id
in your/workspaces/:id
route is merely symbolic since you're actually loading the workspace via the user's cookie. So, the displayed workspace is whatever is stored in the cookie and not what is provided via the route parameter id. Things do seem to still be working, the issue is that the id in the url can differ from what is actually displayed. Which, can lead to issues if you were to ever actually use the url's id for something.So, to prevent that possibility, I would either
Stick to the cookie and get rid of the
/:id
in your/workspaces/:id
route to instead have something like:/workspace
or/workspaces/show
Or, drop the cookie and instead use the id route parameter.
To add, there isn't an issue with using
/workspaces/:id/active
to set the active workspace. The issue is that the new/workspaces/:id
route that shows the workspace has two spots it could pull the id from; the id route parameter in the url and the cookie, and those can differ from one another.Here's a video walkthrough of the behavior I was seeing on your
feat/workspace-roles
branch. This shows how user-a could request workspace b via/workspaces/2
but actually still saw workspace a; meaning the id in the url did not match the workspace used.Here's another walkthrough of the behavior I saw on your
main
branch. In this branch everything works fine.Hope this helps!!
-
Replied to Hi! I'm trying to add a new confirmPassword field only for validation...
Hi usources!
Yeah, what you're doing there is perfectly valid, and what I do in most cases where I don't need to omit a ton of fields. Alternatively, you could use an omit utility, like what is offered from Lodash, but what you have is absolutely fine!
-
Upvoted comment Hi Tom, I'm currently building a task management application...
-
Replied to Hi Tom, I'm currently building a task management application...
Hi emilien! I think I'm following correctly, but let me know if this sounds off base.
So, we manage this with our organizations by querying the active org through the authenticated user, which ensures the user is a member of the org. If the user sets an id of an org that they aren't a member of, our query won't find a match and will instead overwrite the user's active org to the first org found that they're a member of.
For our application, the active organization is determined via a cookie. It sounds like you might have this set up as a route parameter.
If that's the case, ensure you attempt to query the workspace through the authenticated user. If a workspace is not found, you'll want to either:
Throw an unauthorized exception to prevent the user from working with the workspace. You can then offer some button to take them back to safety.
Or, find a valid workspace for the user and redirect them to it. You'll also want to notify them that they did not have access to the previously requested workspace.
The first option there probably being the best as it will ensure the user knows what happened. You don't want to continue onward as the URL contains an invalid id for the user, which could accidentally be used to load something or on subsequent requests sent from whatever page is rendered as a result.
Hopefully that's at least a little on point and helps a little. If not, please feel free to share your query/flow, or a representation of it, and I can try and see if anything stands out.
-
Upvoted comment Hii, there is a bug, clicking on 'next lesson' redirects to ...
-
Replied to Hii, there is a bug, clicking on 'next lesson' redirects to ...
Hi usources! Apologies about that, and thanks so much for letting me know! Should be fixed up now! 😊
-
Replied to Yeah. In my app I wasn't using persisting layouts and was just...
Awesome, happy to hear you were able to get it working! Anytime!
I have a few lessons on pagination (linked below), but nothing to that extent at present, unfortunately. I debated on whether to include a table with pagination, sorting, and filtering in this series but opted against it due to the series already being lengthy. I'm thinking I might do that as an aside series though.
-
Upvoted comment Nevermind, found a repo, problem solved.
-
Upvoted comment How do you handle re-rendering? If you put toast-manager into...
-
Replied to How do you handle re-rendering? If you put toast-manager into...
Hi Pavlo! In Inertia, the built-in layout layer is positioned above the page component. This means that the instance of the layout component persists across page changes, as long as the page is linked to using Inertia’s Link component.
Since we're using the built-in layout layer in Inertia & our
ToastManager
lives within ourAppLayout
andAuthLayout
, we're achieving exactly that. The only exception would be if we switch between those layouts - in which case you could use nested layouts to resolve that if needed. -
Upvoted comment Thank you very much for this detailed response; it will help...
-
-
Upvoted comment Thanks! I simplified it by adding an is_default column to the...
-
Replied to Thanks! I simplified it by adding an is_default column to the...
Anytime! Awesome, that's a perfect solution!! 😊
-
Replied to Hi, thank you very much for this content, it's really very well...
Hi Nicolas! Thank you so much for your kind words!
Funny enough, we'll be following this series up by adding an API to it. It'll be a quicker series than this one, and we won't be adding all the features via API, but we'll structure it as though we were.
I personally would stick to actions, services would be a like alternative, because it provides a single location for our operations to live. It allows us to minimize operational duplication between the web & API layers, which in turn simplifies any refactoring needs down the road.
As for the routes, with this application in mind, yes I would duplicate those so that we have a route definition specific to the web & API layer. For the web layer, it is unlikely to ever need to do versioning as everything can be updated at the same time. The API layer, however, would likely need versioning so we don't randomly break the clients using the API with changes, with the assumption they won't be updated along with the API itself. Additionally, the middleware between these two are likely to be different and our API layer may need to be more complex with filtering and the like. Because of those things, I would also have separate controllers for the API layer as well.
However, if none of the above are issues for an API you're building then you don't need to do that. You can instead use a single route definition for both with a single controller that uses content negotiation to determine whether the client needs an API or a Web/Inertia response. You can always separate the routes/controllers between the web and API down the road. The approach there will vary depending on the project your building and its requirements.
Hope this helps!
-
Published lesson Setting Up Secondary TailwindCSS Config & CSS File for our Landing Page
-
Published lesson Restricting Login Attempts with Rate Limiting
-
Published lesson Clearing Login Attempt Rate Limits on Password Reset
-
Upvoted comment Is there a simple way to query on a relation's relation? For...
-
Replied to Is there a simple way to query on a relation's relation? For...
Yeah, if I'm following correctly, you could probably achieve that with nested
whereHas
calls. This builds outWHERE EXISTS
SQL clauses to query based on a relationship's existence.const user = auth.use('web').user! const organization = await Organization .query() .whereHas('users', (users) => users .where('users.id', user.id) .whereHas('setting', (setting => setting .where('name', 'default organization') .whereRaw('settings_id = organizations.id') // use db names in whereRaw ) ) .first()
Copied!If I'm thinking about this correctly, this would query the first organization where the id matches that of the authenticated user's
user_settings.settings_id
and the matcheduser_settings.name
is alsodefault organization
.Alternatively, you could query for the
user_settings
id first, then use that to query the organization.let organization: Organization const user = auth.use('web').user! const defaultOrganization = await user .related('setting') .query() .where('name', 'default organization') .select('settingsId') .first() if (defaultOrganization) { organization = await user .related('organizations') .query() .where('id', defaultOrganization.settingsId) .first() }
Copied! -
Replied to Hi Tom, I usually work with SQLite or Postgres, but I was wondering...
Hi n2zb! Yeah, absolutely! I haven't done it myself, but it is definitely possible. AdonisJS is still using NodeJS at the end of the day, so you could use Firebase's NodeJS SDK so long as it works with NodeJS v20 or later & supports ESM.
Now - it won't work out of the box with a few AdonisJS packages that rely on Lucid, like:
Auth - though it can be configured with a custom guard
Bouncer
Limiter - though it'd work with Redis
Likely others
-
Upvoted comment Hi! I noticed that in your tutorial, you're able to directly...
-
Replied to Hi! I noticed that in your tutorial, you're able to directly...
Hi cortesbr! You shouldn't need to call
toObject()
to access the model's properties; you should be able to directly reference them viamovie.id
, for example. Please make sure you're using at least NodeJS v20. If you are using NodeJS v20 or later, please feel free to share your repository and I can see if anything stands out. -
Upvoted comment Is this really the only clean way to tell Edge to pass this ...
-
Replied to Is this really the only clean way to tell Edge to pass this ...
The second argument of the
route
method is where we'd add route parameters! So, if you need a route for:router.post('/movies/:id/activate', [MoviesController, 'activate']).as('movies.activate')
Copied!You could use the
route
method in EdgeJS to generate it like so:<a href="{{ route('movies.activate', { id: 1 }) }}"> Activate </a>
The third argument is then additional config options, which includes
qs
to add to the URLs query string. So, there are other options you can include beyondqs
to the third argument. Alternatives to this would include, as you've discovered, hard coding the query string outside the route method. You could also use the Route Builder, I believe you'd need to add this as a global to EdgeJS as I don't think it is included out of the box. However, the Route Builder would be AdonisJS solution to your second example!What I normally do, though, is wrap the
route
helper in my own service to make things super easy to read! For example, a usage of my form service would be:<form method="POST" action="{{ form.delete('redis.flush') }}"> </form>
You could also create EdgeJS components for this as well, which I've done in the past so you could do:
@form.delete({ action: route('redis.flush') }) @end
-
Upvoted comment From a UI perspective, if someone has a lot of organizations...
-
Replied to From a UI perspective, if someone has a lot of organizations...
Yeah, that's a great question, and the approach you'd take will vary depending on either expectations or production data as you don't want to solve issues that don't/won't exist.
In our application, my expectation is that no one realistically would use more than 5 organizations. Since it isn't something I foresee being an issue within our application, if anything, just setting an overflow on the dropdown group listing the user's organizations would be the solution.
.organization-select div[role="group"] { max-height: calc(100vh - 230px); overflow-y: auto; }
Copied!We could then do occasional audits on production data to check for use-cases where our expectations may have been wrong and our game plan needs altered.
If, however, production data shows or our expectations were that users would have 10+ organizations we'd want to take a different approach. It wouldn't be a good experience to list that many organizations within the dropdown; 5 would be ideal, 10 would probably be the max we'd want to show.
So, what we could do is add a timestamp column to the
organization_users
pivot table to store when the user last used the organization, maybe calledlast_used_at
. Within ourSetActiveOrganization
action, we'd be sure to update this timestamp for the user's new active organization.Then, within our
OrganizationMiddleware
where we are getting the user's organizations to list in the dropdown, we'd add a descending order for thelast_used_at
column and limit the results to 5. Resulting in the user's last 5 used organizations being shown in their dropdown. Then, we'd add one more option to their dropdown called "View All Organizations." This could then link to a new paginated page listing all the user's organizations, again defaulting this list to a descending sort by thelast_used_at
.Alternatively, you could just let the user set their own preferred ordering like we did with our difficulties, access levels, etc.
-
Published lesson Rolling Our Own Authorization Access Controls
-
Published lesson Applying Our Server-Side Authorization Checks
-
Published lesson Applying Our Authorization UI Checks
-
Replied to Hello Tom, what's the difference between imports using subpath...
Hi n2zb! Imports using the
@
character, like@adonisjs/core
are actual NPM packages/dependencies installed within our application. The@
character in NPM designates a scoped package. Scoped packages provide a way for the package maintainer to keep all their packages grouped together within the NPM registry. Additionally, it also provides some verification to installers as once a scope is claimed, one the user/organization that claimed the scope may use it.For example, since the AdonisJS Core Team claimed
@adonisjs
only they can register packages using the@adonisjs
scope on NPM.The
#
imports, on the other hand, are an actual NodeJS feature called Subpath Imports. These are path aliases pointing to actual files within your project. Let's say we're importing a model:import User from '../models/user.js'
Copied!Without using a path alias, we need to traverse to the directory our models are within, for example using
../
to go back a directory. We also need to use the.js
extension, required by ES Modules as they resolved and cached as URLs.However, if we define a Subpath Import for our models:
{ "imports": { "#models/*": "./app/models/*.js" } }
Copied!NodeJS will use this as a reference to fill in the blanks, allowing us to drop the relative paths to the files we're after and simplifying our imports.
import User from '#models/user'
Copied!Hope this helps clear things up!!
-
Replied to Hi Tom, why do you define an overlay on redis with the CacheService...
Hi n2zb! We started our
CacheService
without using Redis, but rather an in-memory key/value store. We kept using theCacheService
even with Redis for a couple reasons.I felt it would be easier to understand and follow by refactoring our preexisting service to use Redis rather than directly replacing the service with Redis. That way the before/after is all in one file.
To keep the JSON parse & stringify calls abstracted to the service's get & set methods.
When we have things like this that may be swapped out at some point, I like to keep them abstracted in my code to simplify any updates down the road. For example, if this lesson were released today we would've used Bentocache rather than the AdonisJS Redis package directly as Bentocache is specially attuned for caching with Redis - and will be used for the official AdonisJS Cache package coming soon.
So yes, you're pretty spot on! 😊
-
Upvoted comment Tailwindcss 4.0 is released and it does not work out of the ...
-
Replied to Tailwindcss 4.0 is released and it does not work out of the ...
Yes, thank you very much cinqi! When v4 launched I added a note to the top of this lesson, but failed to realize the plugin Shadcn-Vue uses likely isn't up-to-date with that yet. I've updated my note to recommend sticking to v3 for now when following this series.
I also have a patch to the video being rendered as we speak, which I should be able to get updated tomorrow.
-
Upvoted comment I've been working with Laravel since version 7 but Adonis is...
-
Replied to I've been working with Laravel since version 7 but Adonis is...
I'm really happy to hear you're enjoying AdonisJS, mrs3rver!! 😃
Thank you very much, I greatly appreciate that!!
-
Upvoted comment very good
-
-
Upvoted comment Thanks!
-
-
Upvoted comment I think the button color is coming from inertia_layout.edge....
-
Replied to I think the button color is coming from inertia_layout.edge....
Ah okay! Do you still have the TailwindCSS CDN & project presets within your
inertia_layout.edge
file? That could be clashing with the config we set up in lesson 1.4. I think we also clear those defaults out in that lesson as well.If you'd like some additional eyes on it, please feel free to share a link to the repo! 😊
-
Published lesson Canceling an Organization Invite
-
Published lesson Removing an Organization User
-
Published lesson Refreshing Partial Page Data
-
Upvoted comment I have but the destructive doesn't seem to change my error to...
-
Replied to I have but the destructive doesn't seem to change my error to...
That's odd - do the tailwind classes work okay? Maybe your colors got adjusted, if you followed the Shadcn-Vue setup one-to-one, the colors used by Shadcn-Vue should be defined inside our
app.css
file. Do your colors match mine here? Otherwise, I think the other option in the Shadcn-Vue setup puts them in thetailwind.config.js
file. -
Upvoted comment When you say we might want to add a referrer policy to this ...
-
Replied to When you say we might want to add a referrer policy to this ...
Hi Arron!
Great question, I now wish I would've injected this into the lesson; I think I'll make a note to do that.
The Referrer-Policy mentioned is a response header. So, in the controller rendering this page you'd add it using the HttpContext's response, like below. This is a step recommended by OWASP.
async reset({ params, inertia, response }: HttpContext) { const { isValid, user } = await VerifyPasswordResetToken.handle({ encryptedHash: params.hash }) response.header('Referrer-Policy', 'no-referrer') // 👈 return inertia.render('auth/forgot_password/reset', { hash: params.hash, email: user?.email, isValid, }) }
Copied! -
Completed lesson Forgot Password & Password Reset
-
Upvoted comment Thank you. 👍
-
-
Upvoted discussion alpine js and edge js
-
Replied to discussion alpine js and edge js
This was answered on Discord, but I'll go ahead and answer here in case others come across it wondering the same.
EdgeJS renders HTML markup on your AdonisJS server while AlpineJS mutates the markup in the browser. Once the markup reaches the browser, EdgeJS has already done its job and is out of the picture, so what is being asked isn't possible.
Instead you'll need to use a client-side solution, like Tuyau, if you want to generate routes. Or, you can just hard code the route and use AlpineJS to populate the id that way.
<form :action="`/reviews/update/${selectedReview}`"> </form>
-
Published lesson Listing Current Organization Members
-
Published lesson Sending an Invitation to Join Our Organization
-
Published lesson Accepting an Organization Invitation
-
Published lesson Adding the Organization Invite User Interface
-
Upvoted comment Hello,With useTemplateRef, I’m encountering type errors with...
-
Replied to Hello,With useTemplateRef, I’m encountering type errors with...
Hi gribbl!!
So, I looked into this a little bit, and it seems like everything TypeScript related for these components that are auto-imported via unplugin-vue-components begins working if you add the generated
components.d.ts
file into the/intertia/tsconfig.json
include
array, like below.{ "extends": "@adonisjs/tsconfig/tsconfig.client.json", "compilerOptions": { "baseUrl": ".", "jsx": "preserve", "module": "ESNext", "jsxImportSource": "vue", "paths": { "~/*": ["./*"], } }, "include": ["./**/*.ts", "./**/*.vue", "../components.d.ts"] }
Copied!Here's the result:
I'm going to go tack a note about this into lesson 1.4, where we setup unplugin-vue-components!
-
Upvoted comment The import for the UserDto works fine in my inertia.js file,...
-
Replied to The import for the UserDto works fine in my inertia.js file,...
Hi Aaron! Maybe try giving VSCode a reload by hitting cmd + shift + p then select for "Developer: Reload Window"
There's a bug in VSCode that creeps up every once in a while that causes Vue files to lose intellisense & auto imports. Even You (Vue's creator) posted about it a few months ago; you might be getting caught in that bug here. Reloading usually resolves it when I run into it.
-
Upvoted comment Awesome thanks! Will try it out :)
-
-
Upvoted comment Is there an existing equivalent of setters? For example, I'd...
-
Replied to Is there an existing equivalent of setters? For example, I'd...
Hi Rafal! Yeah, both are possible! Just like getters, setters can't be asynchronous. However, methods can be asynchronous.
export default class User extends BaseModel { // ... @column() declare active: boolean set isActive(value: boolean) { this.active = value } deactivate() { this.active = false } } const user = new User() user.isActive = false user.deactivate()
Copied! -
Upvoted comment If we want to define single or multi column indexes for our ...
-
Replied to If we want to define single or multi column indexes for our ...
Yeah, you can do that via a migration! There is an
index
anddropIndex
method for that. The KnexJS documentation has more info on the options these accept, which will vary depending on your database driver.import { BaseSchema } from '@adonisjs/lucid/schema' export default class extends BaseSchema { protected tableName = 'index_examples' async up() { this.schema.alterTable(this.tableName, (table) => { // single table.index('column_one') // multi-column table.index(['column_two', 'column_three']) // custom named table.index(['column_four'], 'custom_index_name', { /* additional opts */ }) }) } async down() { this.schema.alterTable(this.tableName, (table) => { // single table.dropIndex('column_one') // multi-column table.dropIndex(['column_two', 'column_three']) // custom named table.dropIndex(['column_four'], 'custom_index_name') }) } }
Copied! -
Upvoted comment @tomgobich I followed your instructions to have the unplugin...
-
Replied to @tomgobich I followed your instructions to have the unplugin...
Hi, csaba-kiss!! You can try registering radix-vue's unplugin-vue-components plugin to see if that helps. The caveat here is you'll likely need to use the original name defined in radix-vue, rather than renamed components from shadcn-vue.
For example, to auto-import, you'd probably need to use the name "SplitterPanel" rather than shadcn-vue's "ResizablePanel."
I noticed a similar issue when rebuilding the Adocasts CMS and opted to import the components directly instead.
import Components from 'unplugin-vue-components/vite' import RadixVueResolver from 'radix-vue/resolver' export default defineConfig({ plugins: [ vue(), Components({ dts: true, resolvers: [ RadixVueResolver() // RadixVueResolver({ // prefix: '' // use the prefix option to add Prefix to the imported components // }) ], }), ], })
Copied! -
Replied to Thank you for this great tutorial. I'm a beginner but I managed...
That's awesome to hear marion-huet!!! Thank you for watching! I hope you enjoyed and also found everything easy to understand and follow.
-
Replied to Thank you for taking the time to explain this to me, it's much...
Awesome, I'm glad to hear that, gribbl!! Anytime!!
-
Upvoted comment No problem at all. Thank you for your response.😊
-
-
Anniversary Thanks for being an Acocasts member for 4 years
-
Published lesson Allowing Users to Safely Update Their Account Email
-
Published lesson Alerting Users When Their Account Email Is Changed
-
Published lesson Account Deletion & Cleaning Dangling Organizations
-
Published lesson Updating & Deleting an Organization
-
Replied to Thanks for that explanation. I got an idea in 5.3 of how large...
Anytime!! Yeah, they grow pretty easily & quickly!! lol 😅
-
Upvoted comment What's the benefit of using actions instead of keeping that ...
-
Replied to What's the benefit of using actions instead of keeping that ...
As a whole, by using actions you're:
Giving the operation a name & scope, which improves the ability to scan your controllers.
Gives us the ability to easily reuse the code if needed. It is considered bad practice to call one controller method from another, as controllers are meant to handle routes.
On top of reusability, if we need to update an operation later on, we only have to perform that update in one spot rather than each spot it is performed in the controllers.
As you said, it gives us a separation of concerns. If we're performing an action, we know we can find it within our actions folder, under the resource/section it is for.
Note that actions are very similar to services. To compare, you can think of each subfolder within
actions
as a service class and each file within the subfolder as a service class method. The main difference here though is actions allow us to easily define steps within the actions whereas that'd need to be additional methods inside a service, which can make things a little messy.A good example, though the code for this is from a future lesson releasing soon, is the account deletion controller & action. The controller is easy to scan. The operations have a designated location & scope and can make use of subactions. We're reusing our
webLogout
method, meaning we can easily update just thewebLogout
method if we ever need to adjust how we handle logouts. For example, if you're tracking user sessions, you might need to mark that session record in the database as logged out in addition to actually logging out the user.@inject() async destroy({ request, response, session, auth }: HttpContext, webLogout: WebLogout) { const user = auth.use('web').user! const validator = vine.compile( vine.object({ email: vine.string().in([user.email]), }) ) await request.validateUsing(validator) await DestroyUserAccount.handle({ user }) await webLogout.handle() session.flash('success', 'Your account has been deleted') return response.redirect().toRoute('register.show') }
Copied!I've worked with code, and written my fair share, where the controller method gets unwieldy large because all the operations are done directly in the controller. It might seem small at first, but requirements change, time gets crunched, and things slowly grow over time.
Hope this helps!
-
Upvoted comment I don't know why, if it's just me or Windows, but I'm getting...
-
Replied to I don't know why, if it's just me or Windows, but I'm getting...
Are you using NPM directly within Powershell? Powershell reserved the
@
character for their splatting feature. Which means you'll either need to escape that character or wrap it in strings, as you found.We're using DTOs because getting an accurate serialized type for Lucid models isn't currently possible. If you use the model as a type directly in Inertia, the type will include things you don't have access to in your frontend, like
.merge({})
,.save()
,.related('organizations')
, etc.If you use just the model's attributes, that won't include relationships, extras, and also won't take into account serialization alterations on the model like
serializeAs
.So, currently, it is due to technical constraints in Lucid and would require some hefty refactors to remedy. Yeah, if you were trying to do end-to-end types with an API you'd run into the same constraint.
-
Upvoted comment I hadn’t seen the schedule page. Nice ! The idea of an API is...
-
Replied to I hadn’t seen the schedule page. Nice ! The idea of an API is...
Yeah, those seem to be the areas most struggle with on APIs 😊. Using the PlotMyCourse application also gives us a cool opportunity to cover authentication with a non-user entity.
-
Upvoted comment No problemo.That's strange. I have the exact same code as you...
-
Replied to No problemo.That's strange. I have the exact same code as you...
I'm so sorry, my head was in the wrong spot! You're absolutely right, the
/storage
gets added to the saved value in the database so theunlink
should be:await unlink(app.makePath('.', movie.posterUrl))
Copied!Terribly sorry for the confusion there! I'll make a note to correct this lesson.
-
Replied to Hello. I believe that with recent versions of Vue, it is possible...
Hi gribbl! Thank you for sharing! Yeah, I had missed a number of Vue 3.4's enhancements. I apologize we didn't use any of them in this series. As for 3.5's enhancements, this codebase is still on 3.4 - I don't want to update any packages mid-series until we get to the end where we'll focus on Inertia v2's changes.
-
Replied to Not a problem brother, appreciate the work you're putting into...
Thank you, yunus! I very much agree in regards to using Inertia over meta frameworks when you need the full stack! 😊
As for deployment, please see my comment here. The TLDR is it's coming, but not something I do myself all that often. So I just need to dedicate some time to do proper R&D to do some lessons justice. 😁
-
Replied to Hi! Just wanted to share that a new update for @adonisjs/inertia...
Yes, thank you for sharing!! Once completed, we'll be adding a new module to the end of this series covering what's new in Inertia v2 and the AdonisJS adapter and I'll circle back and add/edit some lessons to include notes for outdated things like this.
-
Published lesson New Unique & Exist Validation Overloads in AdonisJS 6
-
Upvoted comment It would be great if the right hand sidebar showed completed...
-
Replied to It would be great if the right hand sidebar showed completed...
Yes, it absolutely would, thank you very much kite!! I have no clue how I overlooked that. I'll try and get this added tomorrow after work!
-
Upvoted comment Im following along using SolidJS, but got stuck at the auto ...
-
Replied to Im following along using SolidJS, but got stuck at the auto ...
Hi yunus! Apologies for the delayed response! I'm not familiar with the SolidJS ecosystem in the slightest, so I unfortunately do not know. You might have luck using unplugin-auto-import, that is what unplugin-vue-components utilizes, but I'm not certain whether it'll work nor how it'd need to be configured.
-
Replied to The Vue ecosystem is fantastic. I’ve also set up Prettier with...
Yeah, I absolutely love Vue & it's ecosystem!! Thank you for sharing these plugins' gribbl!! 😊
-
Upvoted comment Thank you for this course. I’ve learned so much, but there’s...
-
Replied to Thank you for this course. I’ve learned so much, but there’s...
Thank you for watching, gribbl! I'm ecstatic to hear you learned a lot from it and are loving AdonisJS!!
I discovered AdonisJS back in the early AdonisJS 4 days. I was previously working with Laravel, but I work primarily with JavaScript and found AdonisJS searching for a JavaScript alternative to Laravel. 😊
I've had a lot of requests for some API content, so we'll be doing a series on adding an API to the PlotMyCourse application we're currently making in the Building with AdonisJS & Inertia series. On top of that, we'll also be doing a todo series, showing how to make a todo app in a bunch of different ways with AdonisJS. I have some other ideas on our schedule page, but those are more open to changes before we get to them. I like the idea of an e-commerce site, I'll add that to the ideas, thank you for the suggestion!! 😊
Happy New Year to you as well!! 🎉
-
Upvoted comment Hello,I'm going to bother you again. 😆 I think there’s still...
-
Replied to Hello,I'm going to bother you again. 😆 I think there’s still...
Yep - you're absolutely right, sorry about that! Tacking one more property into the object specifically to uniquely identify the row would fix it up and
Math.random()
is usually what I reach for as well for those use cases.I like to use the below because the
_
prefix ensures it can also be used in theid
attribute if needed.const uniqueId = '_' + Math.random().toString(36).substr(2, 9)
Copied! -
Upvoted comment I don’t have a public directory, only storage at the root of...
-
Replied to I don’t have a public directory, only storage at the root of...
Sorry for the delayed response! Are you able to share the repo url? Neither should be the case, and I can't immediately think of anything that would cause either of those.
-
Replied to Thank you so much for your detailed response, I really appreciate...
My pleasure, Mohamed!! 😊
-
Published lesson Creating the Settings Shell
-
Published lesson User Profile Settings
-
Replied to Hello,When I try to access uploaded file, for example /storage...
Hi Gribbl!
If you're able to directly access the file via the URL without going through a route definition, that leads me to believe the file is actually within your
public
directory. Files within this directory are made accessible by the AdonisJS static file server.That would also explain the "no such file" error on the attempted deletion, as
app.makePath('storage')
is going to be looking in the~/storage
directory directly off your application root, not within thepublic
directory.Can't say for certain though - just making an educated guess based on the info provided.
Hope this helps!
-
Completed lesson Using A Wildcard Route Param to Download Storage Images
-
Upvoted discussion Will deployment be covered?
-
Replied to discussion Will deployment be covered?
Hi Mohamed!
Good question about production deployment! While I don't currently plan on including it in the "Building with AdonisJS & Inertia" series, it's absolutely on my radar and something I plan to cover in the future.
It's not my strongest area, and I don't regularly do it, so to sufficiently cover security concerns I need to invest some time into learning enough to do it justice. I currently use Cleavr, which does all that for me. After investing some R&D time, I may decide to tack it onto the end of the Inertia series or cover deployment in a series of its own.
-
Upvoted discussion How to classify Schema nama in adonisjs 6 migration file?
-
Replied to discussion How to classify Schema nama in adonisjs 6 migration file?
Hi sivaclikry! As of right now, there is no way to generate models and migrations from an existing database. So, you'll want to manually define the models and migrations you'll need.
I have a package that is a work in progress, you're welcome to try it. It'll generate models for you, but it doesn't currently do migrations. Right now, it'll work best with MySQL & PostgreSQL.
To install, you can run:
npm add @adocasts.com/generate-models
Ensure your db connection is defined and working in your
.env
, then generate with:node ace generate:models
-
Published lesson 3 Easy Ways to Split Route Definitions into Multiple Files in AdonisJS 6
-
Published blog post Testing Cloudflare R2 for Video Storage
-
Upvoted comment Please Share me how to work with Transmit inertia vue.
-
Replied to Please Share me how to work with Transmit inertia vue.
Hi laithong! I haven't yet worked with Trasmit, but I don't believe using Inertia should change its implementation any from what is covered in the docs.
The server-side installation would be the same as covered in the documentation; installing the packages, configuring your Redis connection, registering the routes, and defining the channels.
Then, you'd define the client initialization inside a file within your Inertia directory, somewhere like
~/inertia/lib/transmit.ts
, and import it where needed. I imagine the client will only work client-side in the browser. So, you may want to ensure whatever imports your transmit file is running only on the client and not using SSR. I'm not certain on that though. -
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
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
Then
node ace configure @adonisjs/lucid
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
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!
-
-
Completed lesson Setting Up TailwindCSS, Shadcn-Vue, and Automatic Component Imports
-
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)
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.