#21 | Factories & Seeders - AdonisJS 7
Chapters41
Introduces the concept of seeders as a way to preload an application's data, analogous to migrations, and mentions Cedars as the mechanism used.
AdonisJS 7's Cedars and factories let you seed roles, users, and dummy data across environments with reproducible, tested setups.
Summary
Adocasts’ latest session on AdonisJS 7 walks through creating and seeding default data with Cedars, including a practical approach to preloading roles like user and admin. You’ll learn to generate a default cedar with node ace make cedar, place it under database/Cedars, and implement a run method to seed initial data. Arming your app with a Roles enum (roles.ts) helps ensure consistent IDs (user = 1, admin = 2) and prevents accidental elevation of privileges. The video covers hard-coding IDs for initial bootstrap, wiring enums via imports, and utilizing the imports mapping in package.json for clean path resolutions. AdonisJS’ db seed can be run in interactive mode (node ace db seed -i) to select cedars, with a reminder that running migrations after seed requires migration fresh to reset the database. The host also expands on environment gating so default seed data runs in development but is skipped in production. To populate more realistic data, the talk introduces factories: building user, profile, and challenge factories with Faker-powered fields and model relationships, plus techniques like create, make, and the tap method to tailor relationships on the fly. A practical demo shows generating 50 challenges, 10 creators, and six participants per challenge, all tied to real user profiles. The session ends with a callout to deeper factory concepts available in AdonisJS testing guides and an invitation to explore more advanced factory usage in the series “Testing with Yappa.”
Key Takeaways
- Cedars provide a repeatable, scriptable way to seed preliminary data in AdonisJS 7, similar to migrations.
- Create a default cedar (node ace make cedar) and place it under database/Cedars to bootstrap roles and other essential data.
- Use a Roles enum (enums/rolls.ts) with explicit IDs (user = 1, admin = 2) to ensure predictable seeds and avoid superuser risks.
- Gate cedars by environment (start/start EMV enum) so development seeds don’t run in production.
- Factories enable realistic test data: define text and numeric fields with Faker, establish relationships (profiles, creators, participants), and seed multiple records efficiently.
- Interactive seeding (node ace db seed -i) helps pick which cedars to run and avoids unintended migrations or seeds during development.
- You can streamline imports with package.json “imports” mappings to simplify cross-folder references and keep seeds clean.
Who Is This For?
Developers using AdonisJS 7 who want reproducible seed data for development and demos, and QA/test engineers who rely on realistic factory-generated records to test features and relationships.
Notable Quotes
"Cedars allow us to populate our database with preliminary data."
—Definition of Cedars and their purpose in seeding data.
"This just takes in as an argument the name of the cedar that we want to create."
—How to create a cedar with the CLI.
"We can gate these cedars to specific environments."
—Environment-specific seeding to avoid production data contamination.
"Factories are wrappers around our models to aid with creating real database records with fake data inside of them."
—Introduction to factories and their role in seeding and testing.
"Now, for our default Cedar here, we want this to run in any environment because every environment is going to need our roles to exist in order for our application to work."
—Reason for universal seed in the default cedar.
Questions This Video Answers
- How do I seed initial roles in AdonisJS 7 using Cedars?
- How can I run AdonisJS 7 seeds in interactive mode and select which cedars to execute?
- What are the best practices for using factories in AdonisJS 7 to generate realistic test data?
- How do I gate seed data by environment in AdonisJS 7 and why is it important?
- How do I set up an enum-based ID system for seeds in AdonisJS 7 to avoid hard-coded IDs?
Full Transcript
In the last lesson, we hopped into a ripple session to make a role so that we could eventually create a user. It's not ideal to have every teammate pull down our project and then do that manually, though. So, instead, it would be great to have an application's roles created, similar to the process of our migrations. That's where Cedars come into play. Cedars allow us to populate our database with preliminary data. We might need preliminary data for a few different reasons like default data required by our application similar to our roles development environments to get teammates up and running quickly with stubbed data or resetting a demo environment to showcase our projects to prospective clients.
As you might have guessed, we can go ahead and create a cedar by jumping into our terminal here and using the ACL's node ace make cedar command. Similar to make model and make migration, this just takes in as an argument the name of the cedar that we want to create. And within the cedar, we're going to be creating our roles. So every application is going to need to run this. So let's go ahead and call it something like default to get our default state up and running for our application. Once we create that, it's going to plop it within our database directory underneath a new Cedars folder.
And it's going to call it default cedar. If we go ahead and scroll down to that location and open it up, it's going to look a little something like this. Exactly as this comment right here states, all that we need to do is create our preliminary data within this run method. And then this run methods run whenever we run our cedars. So if we wanted to go ahead and create our user and admin roles, we can go ahead and import our role model and then call the create many method. Recalling to the last lesson, this accepts an an array of objects where those objects represent our role.
So all we need here is a name of user for our first role and then a name of admin as our second. We're going to create user first because within our user table migration, we set the default role to one. And for that, we want our user created first. So that that is our default role there. In the vast majority of cases, you're going to want to let your database set the records IDs. However, for our roles here, since we have that one hardcoded by default within our role ID of our users table, it would be great to force the role that we're creating here to have an ID of one as an extra precaution there.
we're not going to want to allow an ID of one to possibly have superpowers like our admin at any point in time. Additionally, we might also need to reference our role ids elsewhere inside our application. So having a hard set of those just simplifies things a little bit further. So within our app directory, let's go ahead and create a brand new directory by right-clicking and hitting new folder. And we'll call this enums. Inside of here, we'll create a new enums file called rolls.ts. And then we can export a const called roles and set that equal to user with a value of one and admin with a value of two.
And let's also add as const to ensure that this is read only and these values can't be overwritten by anything. Once we have that, we'll jump back into our default cedar. And all that we need to do is make use of those keys within our create many array here. So we'll hard set an ID, import our roles enum, and then reference our user value there, which is one. We'll do the exact same thing here for our admin, just like so. And I think the squiggies are just formatting at this point. And there we go. Now, do note that whenever we imported our ROS, it did a relative import going back to directories and then into our app enums folder.
If you prefer the subpath imports, similar to what's being done here with our models, we can add one within our package.json JSON ourselves for our enums directory. So let's scroll on down to our package JSON. And we've talked about this previously, but we have this imports object right here. All that we need to do is add a new one for enums/star and then point that to from our root of our project/app enumsar.js for any JS file within our app enums directory. Now, whenever we reference, and I'm just going to go ahead and NYX this out and show you exactly how this works.
We'll do command dot and add an import for hash enums/rolls. That's this one right here. So, if you'll recall, the dot slash just kind of serves as a placeholder. And then, Node.js is going to look up within our imports for a mapping that starts with hash enums. It will then replace the value of this import where we have that placeholder and then it will pick up from the stem of this star right there for everything else. So really we're importing from app enums/rolls. This will be a one for one swap, but we can also jump into our users table migration as well and replace this hard-coded one with a rolls user usage right there.
Okay, let's jump back into our Cedar and let's open up our terminal yet again. I'm going to go ahead and clear that out. In order to run our cedars, there is a node ace db seed command. Let's go and take a look at the help options for that real quick. And by default, if we just run that as node dbced, it's going to run all of our cedars within our cedars directory. You can also run this command in interactive mode using hyphen i or hyphen hyphen interactive, and it will plop out a list of all of your Cedars and allow you to select which one should run.
Similar to the files option, it's just going to show you an interactive version of it. Now, if we were to actually go ahead and try and run our cedar, so we'll do node ace dbc seed because we refreshed our migrations just before we ended the last lesson, this is going to work okay here. But had we not done that, instead we would have gotten this unique constraint error that we see here. Or at least if you've already created a role, you're going to get a unique constraint on the ro insertion because we're hard setting an ID of one and two for our two different roles and those already exist inside of our database.
We created them within that ripple session. So to fix this, I'm just going to go ahead and run node ace migration fresh, which will give our database a clean slate run. So now we no longer have any data inside of our database. And that will allow us to do node ace db seed. And this time it will succeed. By the way, we could have done that in a single command via node ace migration fresh or refresh. Both have this option hyphen seed. This will run the node ace migration fresh command or refresh if you run that version.
and then immediately follow it by running our cedars as well. So if we run this, you'll see it ran all of our migrations right up here, created our schema classes, and then ran our cedar right down here. If for any reason you do need to run your cedars in a particular order, they do follow a similar natural sort to our migrations. So you can just prefix them with a 01, 02, 03, whatever order it is that you need them to run in there. There are additional options that you can find within the documentation there as well that I'll have linked down below.
Now, for our default Cedar here, we want this to run in any environment because every environment is going to need our roles to exist in order for our application to work. However, within a cedar, if we're creating just dummy data for a development environment per se, we don't want to run the risk of accidentally running that cedar within our production environment because that contains dummy data and we don't want that in production obviously. So, to avoid that, we can gate these cedars to specific environments. So if we make a new cedar here, we'll do node ace make cedar.
And I'm just going to call this dev. Note the name does not do anything in particular here. It is just merely a name. So I'm going to go ahead and create that one. And we'll create our dev cedar here. To then gate this so that we'll only run it within development. We can add to this class a static environment property. And this is a string array. Within this array, we want to define the different environments that we want this cedar to be able to run within. So if we only want this to run in development, then we can just add development there.
If you're curious about what environments your application supports, you can jump on down to the start and the EMV.ts will have this as an enum to our node EMV array. So it accepts development, production, and test. So those are the three valid environments that we can have within our application. That would be accepted here as well. So now if we were to just blanket run node db seed within our production environment it would only run our default cedar. Adonjs would note that this one's flagged to only run at development and hence if we're running in production it will just skip over this cedar altogether.
Now whenever it comes to creating the dummy data within this cedar we don't want to manually have to create all that data for our development environment by hand like we did with our roles. To fix that we can utilize factories. Factories are wrappers around our models to aid with creating real database records with fake data inside of them. This can be handy in two different scenarios. Quickly seeding development data without manual data entry and aiding with testing, allowing us to create data with breeze on a per test basis. We've shown previously how you can create a factory along with the node make model command to create a model and factory in one go, but there is also a standalone node ace make factory command as well.
And to start, we can go ahead and create one for our challenge model. We can then find our factories within our database factories directory. And when we open up our challenge factory, it's going to look like this. Factories are defined kind of using a builder functionality here by passing in the actual model that we're creating a factory for. Then within the second argument, we have a callback function that we can use to return back an object representation of how we want this model's data to be stubbed using faker or any other mechanism that we want to stub data with.
Within this callback function, then we are provided the factory context. Faker is just one of the properties within there. Now despite our challenge model having an ID creator ID text points created at and updated at properties inside of its table and model within this factory we only need to worry about stubbing our text and points columns so that those two in particular are stubbed with fake data. Our database will automatically handle the ID and Adonjs will automatically handle the created at and updated at thanks to the options that we've provided the column datetime decorator on the columns model representation.
Then the creator ID column relates a challenge to a user and has a foreign key constraint. So we'd be better deferring that to the relationship of this factory which we'll see in a bit. So to start within this object, we'll go ahead and add a text property to it. And you'll note that we get IntelliSense autocomplete here for our properties as well. Then for the value of our text, we can utilize faker to describe what sort of fake value should be used to populate this text column. So, Alpha Faker, we have a number of different categoricalbased options that we can reach into.
There's not really a challenge-based option here, but one that I find kind of fits our purpose a little bit is if we reach into the get category, there is a commit message option that we can use. And we'll just call that like a function. Whenever we create an instance of our factory, it will execute this callback function which will then call the faker method to plop in a text value within here. And Faker will automatically and randomly pick a commit message from its store to apply to the record that we're creating here. Then we can do the same here for our points.
We can reach through again to faker and there is a number category for this and we can define that this should be an intbased value and provide it some options like we want the min value to be one and the max value to be 100 keeping our points value within the range that is determined as valid by our valid data. That then takes us to our relationships. However, before we can have relationships within our factory, we need to have factory representations of those related models. So let's go ahead and create our other two factories. And again, similar to our model situation, the challenge user pivot table doesn't need to have a factory representation because it will be sufficiently represented by a many to many relationship as defined in our models and that will get picked up here appropriately inside of our factories as well.
So jumping back into our terminal, we can clear that out. We'll go ahead and do node ace make factory and we'll need one for our profile. And then we're also going to go ahead and need one for our user as well. Okay, we can clear that out and close it out. Let's go ahead and first jump into our profile factory. Within here, we have a bio and we can do faker and there's a person category and there is a bio option within there to fit that one one. We also have an is public flag within here as well noting whether or not the user's profiles public and for that we can reach into faker and there is a data type category with which we can grab a random boolean.
So this will randomly create enabled and disabled profiles as we create profiles for our user. Finally, then we have our user factory. Similar to how we did with our migration for our role ID, we can go ahead and set a default value here of just roles. Import that and set it to user so that by default every user is created as a user role rather than an administrator. Then for our full name, we have Faker and there's a person category with a full name to match that one to one. for our email. There's similar. So, we have faker internet email.
And then finally, we have the password. Now, for the password, we could use faker internet password. However, since we're in development and using a database on our local machine, we can save ourselves the hassle and actually hardcode a password in here so that we know what the actual user's password is in the event that we need to log in as them. Because by randomly creating a password for our users, there isn't really going to be a way for us to know what the actual password value is. Because as we'll see in the authentication section, our user model is going to protect this password value and offuscate it from us.
So instead, what I'm going to do is just hardcode this to a string of something. So now anytime that we need to enter in a user's password, we just need to enter in something and that is their password. That takes us then to our relationships. By adding relationships to our factory, we're giving ourselves the ability to create a fake challenge with a fake creator and a bunch of fake participants without having to specify any of that data ourselves. We can easily define a factory relationship using the relation method after the define method. But before we build out the factory, this two is going to use IntelliSense.
So we're going to have an autocomplete list of the relationships defined on say our user model here. So we can create one for challenges. Then we need to define a callback function that simply returns back the factory that should handle the creation of this relationship. So our challenges relationship points to a challenge model. So for that we want to point this to our challenge factory to return. Now anytime that we create a user and we tell it to create challenges for that user, it will find this relationship and utilize the challenge factory as we've defined here to create the applicable challenges along with this user as well.
So in addition to our challenges, we also have our challenge created list for our user. And that too is going to utilize our challenge factory. And then finally, we have our profile just like so. There was also a role relationship that we could define, but we're not actually going to need to utilize that in any way. So we'll omit it there. And we're already handling it within our object here with the default value of user anyway. So we're all set on that front. Noting again, you really only need to fill out the relationships that you're actually going to use here, and you also don't need to fill both sides of the relationships out.
However, we're going to go ahead and do that here. So, we'll jump into our profile factory next. We'll work our way back up, and this one just has one relationship to our user, and this is going to utilize our user factory, just like so. Easy peasy. Finally, we have our challenge factory. This one has two. So, we have a relationship for which user created this challenge, and that will use our user factory. And then finally, we have our participants, which will also utilize our user factory as well. Now that we have our factories filled out, we can go ahead and utilize them within our development cedar to create dummy data for our development environment.
And these factories can work relatively similar to our models as well. So for example, if we go and do await and import our challenge factory, note that you want to import the factory, not the model here. The factory will wrap the model and it has additional methods just like our model does. So we have create here in which the factory will work with the model that it represents to create a record using fake dummy data as defined within that object returning inside of the factory. Now create here will both create a factory instance with fake data inside of it and persist it to the database.
If you just want a fake factory instance with data but not have it be persisted to the database. There is also make stubbed and both create and make stubbed here will return back uh the actual challenge instance that was created there. Differences make stubbed will not persist it to the database while create will. We're not going to need that variable at all. So I'm going to go ahead and get rid of that as well. One challenge isn't much for dummy data. So let's go ahead and instead create many. And within create many, we need to specify how many exactly we want to create.
So let's go ahead and create 50 there. It will now create 50 different challenges using our factory with fake data inside of it and then persist all 50 of those to the database. Now our challenges are also going to need a creator with them. So we can utilize our factories relationship to say with these factories that we're creating also go ahead and create a user and assign it to the created by relationship. Now, we don't want one user to have created 50 different challenges. So, we can additionally add in a count here to say that we want 50 different users that have created 50 challenges in total.
Our creators could also use a profile though. So, for that, we actually have a third argument that allows us to reach into the factory builder to define additional statements for the user that we're creating as our created by. So we can do builder dot and just similar to we've done with our challenge here, we can add width to go ahead and create a profile with each user that we're creating to in turn create our challenges. So this one line, well it kind of broke down into a few here, but the single factory usage will create 50 challenges with 50 users, one creator per challenge.
What if we instead wanted a pool of 10 creators to randomly spread across our 50 challenges instead? Well, we could first start by creating our creators. So, we'll do const creators equals await and we'll import our user factory. With our user factory, we'll go ahead and assign each of these creators a profile. And then we'll create many and create 10 different creators. We're then going to want just those creators IDs to simplify things a little bit. So, we'll do creator ids equals and we have back the actual model representation of these creators. So, we can do creators map each creator here as a user.
and we'll return back the users ID. Our challenge factory then becomes a little bit more simplified. We're just going to cut this back to our create menu because instead of creating our creators and profiles for them directly with this create many, instead what we want to do is tap into the models these factories have created prior to them being persisted to our database. So within tap, we get back the actual row that's been created with our factory. This will be our model instance. So here we have our challenge. Then it also gives us our factory context.
So we can pluck out just like we did within our actual factories callback function the faker instance and then define a callback function for this tap method that's able to alter our challenge model instance in any way needed prior to it actually being persisted into the database. So here we can go ahead and reach through to challenge creator ID and set this to a random ID that we have inside of our creator ids array. So we can do faker and there's a helpers object in here that has an array element method. This will pick a random item out of an array that we provided.
So we can pass in our creator ids there. And now a random creator ID will get assigned to each of our challenges from our creators pool of data. meaning that we're going to create 50 challenges in total with 10 different randomly assigned creators. This tap method is run once for each challenge that we're creating with our factory. It then provides us that individual challenges model instance which we're overwriting and then whenever we fall out of the callback function, it will then go ahead and commit that to our database. Finally, we could use some participants with our fake data as well.
So let's chain off of this an additional method with our with method adding in participants. And we'll add six participants per factory that we're creating. And then just like with our creators, we also want to reach through to the builder here to include a profile with those users. All right, so that should do it. Let's go ahead and give that a save. Now we've already run our default cedar. So here we only specifically want to run our dev cedar. So we'll do node ace db seed and I'm just going to do hyphen i to enter into the interactive mode.
We'll hit enter there and as you'll see it will ask us to select using the space bar the cedars that we would like to run. So we just want to run our dev cedar. So I'm going to hit the down arrow and hit space. And you'll note that that little selection toggle right there has changed slightly signifying that it is selected. If I now hit enter, it will run our cedar, but it ran into an unexpected issue. And this factory is not a function. And it ran into it while trying to insert our users. So, it ran into it from our user factory.
Uh, everything looks right here. So, I'm going to jump into our user factory. And oh yeah, okay. See, right there, failed to actually create our profile there as a callback function and instead just returned uh the actual factory directly. So, let's fix that and give that a save. Jump back into our dev seedater here. And let's give that one more go. So, I'm going to clear that out. We'll jump back into our interactive mode and we'll try running our dev seater one more time. Since this failed with uh the very first line in here, it means that it didn't actually create anything.
So, I'm going to go ahead and hit enter. And that should go ahead and go. And look at that go, man. All right. So, you're more than free to go back through and see exactly what all that has done. But if we were to go ahead and at this point boot back up our application and jump back into our browser and give our slash challenges page a refresh. Check that out. We now have 2,634 total points available. And we have should have about 50 challenges in total with just some random commit based text there. If we dive into these, we can see their details.
We can edit them. And each of these challenges also have participants and creators as well that we can work with now too. So, if you're interested in learning more about factories, especially in text context, uh, check out our series testing with Yappa. We go into much further detail with them and cover a little bit more complex situations as
More from Adocasts
Get daily recaps from
Adocasts
AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.




![Power BI Full Course 2026 [FREE] | Power BI Tutorial For Beginners | Power BI Course | Simplilearn thumbnail](https://rewiz.app/images?url=https://i.ytimg.com/vi/GhuPneZhk1Q/maxresdefault.jpg)