CRUD operations
Lucid models make it very easy to perform CRUD operations and also define lifecycle hooks around each operation.
Create
You can create and persist new records to the database by using the static create
method.
import User from '#models/user'
const user = await User.create({
username: 'virk',
email: 'virk@adonisjs.com',
})
console.log(user.$isPersisted) // true
Additionally, you can create and persist new records to the database by first assigning values to the model instance and then calling the save
method.
The save
method performs the INSERT query when persisting the model instance for the first time and performs the UPDATE query when the model has persisted.
import User from '#models/user'
const user = new User()
// Assign username and email
user.username = 'virk'
user.email = 'virk@adonisjs.com'
// Insert to the database
await user.save()
console.log(user.$isPersisted) // true
Also, you can use the fill
method to define all the attributes as once and then call the save
method.
import User from '#models/user'
const user = new User()
// Assign username and email using the fill method and then save
await user
.fill({ username: 'virk', email: 'virk@adonisjs.com' })
.save()
console.log(user.$isPersisted) // true
createMany
Create multiple instances of a model and persist them to the database. The createMany
method accepts the same options as the create
method.
One insert query is issued for each model instance to execute the lifecycle hooks for every instance.
const user = await User.createMany([
{
email: 'virk@adonisjs.com',
password: 'secret',
},
{
email: 'romain@adonisjs.com',
password: 'secret',
},
])
Read
You can query the database table using one of the following static methods.
all
Fetch all the users from the database. The method returns an array of model instances.
const user = await User.all()
// SQL: SELECT * from "users" ORDER BY "id" DESC;
find
Find a record using the primary key. The method returns a model instance or null (when no records are found).
const user = await User.find(1)
// SQL: SELECT * from "users" WHERE "id" = 1 LIMIT 1;
findBy
Find a record by a column name and its value. Similar to the find
method, this method also returns a model instance or null
.
const user = await User.findBy('email', 'virk@adonisjs.com')
// SQL: SELECT * from "users" WHERE "email" = 'virk@adonisjs.com' LIMIT 1;
findManyBy
Find multiple record by one or multiple column name and its value. This method returns an array of model instance or an empty array ([]
).
const users = await User.findManyBy('status', 'active')
// SQL: SELECT * from "users" WHERE "status" = 'active';
const posts = await Post.findManyBy({ status: 'published', userId: 1 })
// SQL: SELECT * from "posts" WHERE "status" = 'published' AND "userId" = 1;
first
Fetch the first record from the database. Returns null
when there are no records.
const user = await User.first()
// SQL: SELECT * from "users" LIMIT 1;
orFail variation
You can also use the orFail
variation for the find methods. It raises an exception when no row is found.
const user = await User.findOrFail(1)
const user = await User.firstOrFail()
const user = await User.findByOrFail('email', 'virk@adonisjs.com')
The orFail
variation will raise an E_ROW_NOT_FOUND
exception with 404
statusCode.
Using the query builder
The above-mentioned static methods cover the common use cases for querying the database. However, you are not only limited to these methods and can also leverage the query builder API for making advanced SQL queries.
The ModelQueryBuilder returns an array of model instances and not the plain JavaScript object(s).
You can get an instance of a query builder for your model using the .query
method.
const users = await User
.query()
.where('countryCode', 'IN')
.orWhereNull('countryCode')
To fetch a single row, you can make use of the .first
method. There is also a firstOrFail
method.
const users = await User
.query()
.where('countryCode', 'IN')
.orWhereNull('countryCode')
.first()
Update
The standard way to perform updates using the model is to look up the record and then update/persist it to the database.
const user = await User.findOrFail(1)
user.lastLoginAt = DateTime.local() // Luxon dateTime is used
await user.save()
Also, you can use the merge
method to define all the attributes at once and then call the save
method.
await user.merge({ lastLoginAt: DateTime.local() }).save()
Why not use the update query directly?
Another way to update the records is to perform an update using the query builder manually. For example
await User.query().where('id', 1).update({ lastLoginAt: new Date() })
However, updating records directly does not trigger any model hooks and neither auto-update the timestamps.
We recommend not stressing much on the extra select
query unless dealing with millions of updates per second and happy leaving the model's features.
Delete
Like the update
operation, you first fetch it from the database and delete the row. For example
const user = await User.findOrFail(1)
await user.delete()
Again, for hooks to work, Lucid needs the instance of the model first. If you decide to use the query builder directly, then the model will not fire any hooks.
However, the direct query builder approach can help perform bulk deletes.
await User.query().where('isVerified', false).delete()
Idempotent methods
Models come with many helpful methods to simplify the record creation by first finding them inside the database and running the create/update queries only when the record doesn't exist.
firstOrCreate
Search for a record inside the database or create a new one (only when the lookup fails).
In the following example, we attempt to search a user with an email but persist both the email
and the password
, when the initial lookup fails. In other words, the searchPayload
and the savePayload
are merged during the create call.
import User from '#models/user'
// User.firstOrCreate(searchPayload, savePayload)
await User.firstOrCreate(
{ email: 'virk@adonisjs.com' },
{ password: 'secret' }
)
fetchOrCreateMany
The fetchOrCreateMany
is similar to the firstOrCreate
method, but instead, you can create more than one row. The method needs a unique key for finding the duplicate rows and an array of objects to persist (if missing inside the database).
import User from '#models/user'
// User.fetchOrCreateMany(key, arrayOfObjects)
await User.fetchOrCreateMany('email', [
{
email: 'foo@example.com',
username: 'foo',
},
{
email: 'bar@example.com',
username: 'bar',
},
{
email: 'baz@example.com',
username: 'baz',
},
])
updateOrCreate
The updateOrCreate
either creates a new record or updates the existing record. Like the firstOrCreate
method, you need to define a search payload and the attributes to insert/update.
import User from '#models/user'
// User.updateOrCreate(searchPayload, persistancePayload)
await User.updateOrCreate(
{ email: 'virk@adonisjs.com' },
{ password: 'secret' }
)
updateOrCreateMany
The updateOrCreateMany
method allows syncing rows by avoiding duplicate entries. The method needs a unique key for finding the duplicate rows and an array of objects to persist/update.
import User from '#models/user'
// User.updateOrCreateMany(key, arrayOfObjects)
await User.updateOrCreateMany('email', [
{
email: 'foo@example.com',
username: 'foo',
},
{
email: 'bar@example.com',
username: 'bar',
},
{
email: 'baz@example.com',
username: 'baz',
},
])
In this example, we use both the email and username as keys to find duplicates. If a row already exists with the same combination of email and username, it will be updated with the new provided values. Otherwise, a new row will be created with the provided values.
import User from '#models/user'
// User.updateOrCreateMany(keys, arrayOfObjects)
await User.updateOrCreateMany(['email', 'username'], [
{
email: 'foo@example.com',
username: 'foo',
},
{
email: 'bar@example.com',
username: 'bar',
},
{
email: 'baz@example.com',
username: 'baz',
},
])