yield User.all()
yield User.query().where('status', 'active').fetch()
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
Lucid is the implementation of Active record which is an architectural pattern of storing and manipulating SQL data as objects. Lucid makes it so simple for you to write non-trivial web applications with ease and less code.
Lucid has support for:
Fluent query builder to query data by chaining javascript methods.
yield User.all()
yield User.query().where('status', 'active').fetch()
Solid support for defining database relations without touching your SQL schema.
class User extends Lucid {
profile () {
return this.hasOne('App/Model/Profile')
}
posts () {
return this.hasMany('App/Model/Post')
}
}
Running queries inside transactions.
Getters/Setters to mutate data on the fly.
Inbuilt support for computed properties.
Database hooks to add domain logic to specific events.
Support to define visible/hidden fields to remove them from the JSON output. The best example is of hiding the password
field from the JSON output.
Let’s start with a basic example of creating a user model and querying users from the corresponding database table.
./ace make:model User
# or with migration
./ace make:model User --migration
'use strict'
const Lucid = use('Lucid')
class User extends Lucid {
}
Yes, that’s all you need to define a Lucid model. Lucid will figure out the database table name using some predefined conventions. For example User model will look for users table.
+---------+-----------+--------------------+------------+
| id (PK) | username | email | password |
+---------+-----------+--------------------+------------+
| 1 | unicorn | unicorns@ages.com | secret |
| 2 | lois | lois@oscar.com | secret |
+---------+-----------+--------------------+------------+
Make sure to use Migrations to set up the users table. Lucid does not create/alter database tables for you.
|
Now let’s say we want to fetch all the users from the users
table. For the sake of simplicity, we will make fetch the users within the routes
file.
const Route = use('Route')
const User = use('App/Model/User')
Route.get('/users', function * (request, response) {
const users = yield User.all() (1)
response.ok(users)
})
1 | The all method will fetch all the records from the database table. Think of it as select * from "users" query. |
Models inherit a handful of properties from Lucid base class, which prevents you from re-writing the same code again and again. Only implement below methods if you want to change the default behavior of a model.
The table name is the plural underscore representation of your model class name.
Model | Table Name |
---|---|
User |
users |
Person |
people |
PostComment |
post_comments |
To override the conventional table name, you can return a value from the table
getter.
class User extends Lucid {
static get table () {
return 'my_users'
}
}
Each model needs to have a primary key which is set to id
by default. Value for primary key is auto-populated by Lucid whenever you save a new model to the database. Also, the primary key is required to resolve model relations.
class User extends Model {
static get primaryKey () {
return 'userId'
}
}
Connection parameter helps you in using different databases connection for a given model.
Database connections are defined inside config/database.js
file. Lucid makes use of default connection defined under the same file. However, you can swap this value to use any defined connection from your database config file.
module.exports = {
connection: 'mysql',
mysql: {
....
},
reportsMysql: {
...
}
}
class Report extends Mysql {
static get connection () {
return 'reportsMysql'
}
}
At times applications relies on uuid
as their primary keys. Since uuids are generated before persisting the database record, they are not auto incremented. So it is important to tell Lucid before-hand about the same.
class User extends Lucid {
static get primaryKey () {
return 'uuid'
}
static get incrementing () {
return false
}
}
Timestamps eliminate the need for setting up timestamps manually every time you create or update a record. Following timestamps are used for different database operations.
Create timestamp defines the database field to be used for adding row creation time to the database table. You can override this property to specify a different field name or return null
to disable it.
class User extends Lucid {
static get createTimestamp () {
return 'created_at'
}
}
Every time you modify a row in a database table updateTimestamp
will be updated to the current time.
class User extends Lucid {
static get updateTimestamp () {
return 'updated_at'
}
}
The deleteTimestamp
behaves a little different from create and update timestamps. You should only return value from this method if you want to make use of soft deletes.
class User extends Lucid {
static get deleteTimestamp () {
return null
}
}
Soft deletes is a term for deleting records by updating a delete timestamp instead of removing the row from the database.In other words, soft deletes are safe deletes, where you never loose data from your SQL tables.
Soft deletes are disabled by default and to enable them you must return a table field name from deleteTimestamp
getter.
You can make use of withTrashed method to fetch soft deleted rows. |
Date format specifies the format of date in which timestamps should be saved. Internally models will convert dates to moment.js instance. You can define any valid date format supported by momentjs.
class User extends Lucid {
static get dateFormat () {
return 'YYYY-MM-DD HH:mm:ss'
}
}
Quite often you will find yourself omitting/picking fields from the database results. For example: Hiding the user’s password from the JSON output. Doing this manually can be tedious in many ways.
You will have manually loop over the rows and delete the key/value pair.
When you fetch relationships, you will have to loop through all the parent records and then their child records to delete the key/value pair.
AdonisJs makes it simpler by defining the visible or hidden (one at a time) on your model.
class User extends Lucid {
static get hidden () {
return ['password']
}
}
class Post extends Lucid {
static get visible () {
return ['title', 'body']
}
}
Query scopes are fluent methods defined on your models as static methods and can be used within the query builder chain. Think of them as descriptive convenient methods for extending the query builder.
class User extends Lucid {
static scopeActive (builder) {
builder.where('status', 'active')
}
}
Now to make use of the active scope, you just need to call the method on the query builder chain.
const activeUsers = yield User.query().active().fetch()
Query scopes are always defined as static methods.
You must append your methods with scope
followed by the PascalCase method name. For example: scopeLatest()
will be used as latest
.
You must call the query
method on your model before calling any query scopes.
Unfortunately, Javascript has no way of defining traits/mixins natively. Lucid models makes it easier for you to add traits to your models and extend them by adding new methods/properties.
class Post extends Lucid {
static get traits () {
return ['Adonis/Traits/Slugify']
}
}
Also, you can dynamically add traits using the use
method.
class Post extends Lucid {
static boot () {
super.boot()
this.use('Adonis/Traits/Slugify')
}
}
Make sure to define traits only once. Redefining traits will cause multiple registrations of a triat, and your models will misbehave. The best place of defining dynamic traits is inside the Model boot method.
|
CRUD is a term used to Create, Read, Update and Delete records from a database table. Lucid models offer a handful of convenient methods to make this process easier. Let’s take an example of managing posts using the Post model.
+------------+-----------------+
| name | type |
+------------+-----------------+
| id (PK) | INTEGER |
| title | VARCHAR(255) |
| body | TEXT |
| created_at | DATETIME |
| updated_at | DATETIME |
+------------+-----------------+
./ace make:model Post
Now let’s make use of the Post Model to perform CRUD operations
const post = new Post()
post.title = 'Adonis 101'
post.body = 'Adonis 101 is an introductory guide for beginners.'
yield post.save() // SQL Insert
The save
method will persist the model to the database. If row already exists in the database, it will update it. Alternatively, you can also make use of the create
method which allows you to pass all the values as a parameter
const post = yield Post.create({
title: 'Adonis 101',
body: 'Adonis 101 is an introductory guide for beginners'
})
Read operation is divided into two segments. First is to fetch all the posts and another one is to fetch a single post using id
or any other unique identifier.
const posts = yield Post.all()
const postId = request.param('id')
const post = yield Post.find(postId)
if (post) {
yield response.sendView('posts.show', { post: post.toJSON() })
return
}
response.send('Sorry, cannot find the selected found')
The update operation is performed on an existing model instance. In general scenarios, you will have an id of a row that you want to update.
const post = yield Post.findBy('id', 1)
post.body = 'Adding some new content'
yield post.save() // SQL Update
Alternatively, you can also make use of the fill
method to pass all new key/values pairs as an object.
const post = yield Post.findBy('id', 1)
post.fill({body: 'Adding some new content'})
yield post.save() // SQL Update
Delete operation is also performed on an existing model instance. If you have turned on softDeletes, then rows will not be deleted from SQL. However, the model instance will be considered deleted.
const post = yield Post.findBy('id', 1)
yield post.delete()
Also, from this point model instance will freeze for edits. However, you can still read data from existing model instance but will not be able to edit it anymore.
const post = yield Post.findById(1)
yield post.delete()
console.log(post.title) // Adonis 101
post.title = 'New title' // will throw RuntimeException
Lucid internally makes use of Database Provider which means all methods from Database provider are available to your models. Also below methods have been added for convenience.
The query
method will return the query builder instance which means you build your queries with the same ease as would do with Database provider.
yield Post.query().where('title', 'Adonis 101').fetch()
It is important to understand role of the fetch
method. Fetch method will execute the query chain but also makes sure to return a collection of model instances.
Which means each item inside the collection array will not be a regular Object. Instead, it will be a complete model instance.For example:
const posts = yield Post.query().where('title', 'Adonis 101')
console.log(posts)
[
{
id: 1,
title: 'Adonis 101',
body: 'Adonis 101 is an introductory guide for beginners.',
created_at: '2016-02-20 17:59:25',
updated_at: '2016-02-20 17:59:29'
}
]
const posts = yield Post.query().where('title', 'Adonis 101').fetch()
console.log(posts.value())
[
Post {
attributes: {
id: 1,
title: 'Adonis 101',
body: 'Adonis 101 is an introductory guide for beginners.',
created_at: '2016-02-20 17:59:25',
updated_at: '2016-02-20 17:59:29'
},
original: { ... }
}
]
Later one is an array of model instances, which has its benefits. We will talk about them in a different guide.
The first
method will return only the first matched row as the model instance. If no row has been found, it will return null
.
const post = yield Post.query().where('title', 'Adonis 101').first()
Find a single row for a given key/value pair.
yield Post.findBy('title', '...')
yield Post.findBy('body', '...')
yield Post.findBy('id', '...')
The find
method is similar to the xref:_find_by_key_value(findBy) method instead it makes use of the xref:_primary_key(primaryKey) as the key for fetching the row.
yield Post.find(1)
Returns an array of all the ids from the corresponding database table.
const ids = yield Post.ids()
The pair
method will return a flat object with a key/value pair of lhs and rhs key. It is helpful in populating the select box options.
const countries = yield Country.pair('code', 'name')
{
ind: 'India',
us: 'United States',
uk: 'United Kingdom'
}
The paginate
method makes it so simple to paginate over database records.
const posts = yield Post.paginate(request.input('page'))
The pick
method will pick the give number of records from the database.
const posts = yield Post.pick(2)
The pickInverse
works similar to the pick
method instead it will pick rows with desc
clause.
const posts = yield Post.pickInverse(2)
The create
method is used to create a new row to the database
const user = yield User.create({ username: 'virk', email: 'virk@adonisjs.com' })
Create/Update a model instance
const user = new User()
user.username = 'virk'
user.email = 'virk@adonisjs.com'
yield user.save()
Create multiple rows at once. This method will return an array of model instances.
const users = yield User.createMany([{...}, {...}])
Lucid also has some handy methods that will throw exceptions when not able to find a given row using find
or findBy
method. Some programmers find it simpler to throw exception and catch them later inside a global handler to avoid if/else
clause everywhere.
const userId = request.param('id')
const user = yield User.findOrFail(userId)
const user = yield User.findByOrFail('username', 'virk')
If you want, you can wrap your orFail
methods inside a try/catch
block, or you can handle them globally inside app/Listeners/Http.js
file.
Http.handleError = function * (error, request, response) {
if (error.name === 'ModelNotFoundException') {
response.status(401).send('Resource not found')
return
}
}
The findOrCreate
method is a shortcut to finding a record and if not found a new record will be created and returned back on the fly.
const user = yield User.findOrCreate(
{ username: 'virk' },
{ username: 'virk', email: 'virk@adonisjs.com' }
)
The withTrashed
method can be used to fetch soft deleted rows.
const users = yield User.query().withTrashed().fetch()
AdonisJs has first class support for running SQL transactions using the Database Provider. Also, your Lucid models can make use of transactions when creating, updating or deleting records.
const Database = use('Database')
const trx = yield Database.beginTransaction() (1)
const user = new User()
user.username = 'liza'
user.password = 'secret'
user.useTransaction(trx) (2)
yield user.save()
trx.commit() (3)
1 | You always make use of the database provider to begin a new transaction. The reason we decoupled transactions from the Lucid models is to offer the flexibility of using same transaction instance of different models. |
2 | The useTransaction method will use the transaction instance to perform upcoming SQL operations. |
3 | The commit method gives you the ability to commit the transaction or rollback it if something unexpected happened. |