* index () {
yield User.all()
}
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.
AdonisJs 4.0, known as (dawn), is a major release of the framework after more than a year. Each major release comes with a handful of breaking changes, which is required to keep the framework fresh and address all issues reported during the year.
This release is very special since much movement has happened in the ES6 world, and Node.js 8.0 has implemented 99% of the language features. Also, the V8 engine has received lots of speed improvements.
The time has finally come to update the code base and start supporting the latest features so that we can all write simpler and more expressive code while taking advantage of all the performance benefits.
Below is the list of changes made due to the availability of ES6 features.
Generator functions using the yield
keyword are no longer supported, and instead, we make use of async/await
.
This means all functions starting with *
need to be replaced with async
, and yield
keywords need to be replaced with await
.
Old way
* index () {
yield User.all()
}
New way
async index () {
await User.all()
}
ES6 allows object destructuring which can be used to pull selected values from an object.
In the newer version of AdonisJs, all route handlers and controller actions receive an object of values instead of receiving multiple parameters with request
and response
.
Old Way
Route.get('/', function * (request, response) {
})
New Way
Route.get('/', async ({ request, response }) => {
})
The directory structure has been tweaked a bit to keep it as flat as possible and also to make the upgrade path easier in upcoming versions.
The bootstrap
directory has been renamed to start
, which performs the same function but is easier to understand.
Also, a bunch of files like http.js
and kernel.js
have been removed since they contained the code to wire the app and boot the server. All this has been moved to a separate package called @adonisjs/ignitor.
The location of Controllers have been changed as follows
app/Http/Controllers → app/Controllers/Http
Having a top level Http
directory does not make sense, since the only thing we store inside it is the Controllers
, so it is better to have Controllers
as the top-level directory and keep WebSocket and Http controllers inside it.
The routes.js
file has been moved from app/Http/routes.js
to start/routes.js
. Since routes are the starting point to your app, they should be in the start directory.
The kernel.js
file, which stores the list of Middleware, has been moved from app/Http/kernel.js
to start/kernel.js
.
The job of the storage
directory was to hold files which are never committed to version control like Github. However, the name itself did not make it clear that the directory holds temporary files. The rename to tmp
makes it more clear.
The addCollection
and addMember
methods have been removed from the resource routing. This is done in favor of keeping routes straightforward since it was never clear in which sequence a new member was added via the addMember
method. For example
Route
.resource('users', 'UserController')
.addMember('active')
The resource creates a route called users/:id
as part of the resourceful routes and adding a new member adds the users/active
route.
It is unclear which action gets called first, since users/:id
will catch all the routes including users/active
.
Instead, you should manually define the other routes.
Route.get('users/active', 'UserController.getActive')
Route.resource('users', 'UserController')
The group method signature has been changed, and now it is optional to pass the name.
Earlier
Route.group('auth', () => {
})
Now
Route.group(() => {
})
The 1st
param is the group name, which is optional now. If you give a group name, then all route names are prefixed with that.
The Route.route
method signature has been tweaked a bit.
Earlier
Route.route('/', ['GET', 'POST'], () => {
})
Now
Route.route('/', () => {
}, ['GET', 'POST'])
Below is the list of breaking API changes in the Request object.
The param/params
methods have been removed in 4.0, and instead, a separate object is passed with all the route params.
Earlier
Route.get('users/:id', function (request) {
const id = request.param('id')
})
Now
Route.get('users/:id', function ({ params }) {
const id = params.id
})
The view layer of AdonisJs now uses Edge over nunjucks which is a home-grown template engine written for AdonisJs itself.
Extending the core of nunjucks was so painful that adding new tags and helpers was becoming hard. Edge has a very minimal developer API, and it is pretty straightforward to extend.
Make sure to check edge guides to learn more about it.
The response.sendView
function has been removed and instead a view
instance is passed to all the HTTP requests.
Earlier
Route.get('/', function * (request, response) {
yield response.sendView('home')
})
Now
Route.get('/', ({ view }) => {
return view.render('home')
})
The authentication engine has remained about the same. This section outlines some of the breaking changes.
The request.auth
method has been removed and instead a dedicated auth
instance is passed to all HTTP requests.
Earlier
Route.get('/', function * (request) {
const auth = request.auth
console.log(auth.currentUser)
})
Now
Route.get('/', ({ auth }) => {
console.log(auth.user)
})
The api
authenticator used to have revokeToken
and revokeTokens
methods, which have been removed and instead you can use the User
model directly to revoke tokens.
Earlier
Route.get('/', function (request) {
yield request.auth.revokeTokens(request.currentUser)
})
Now
Route.get('/', async ({ auth }) => {
await auth.user
.tokens()
.where('type', 'api_token')
.update({ is_revoked: true })
})
Since the tokens
table now keep all sort of tokens like remember tokens and jwt refresh tokens, it is more convenient to use the User
model directly and revoke the required tokens.
A bunch of changes had been made to lucid, the majority of which are improvements, bug fixes, and much-awaited features.
Here is the list of breaking changes.
All models used to fetch the Lucid
namespace to extend themselves. Going forward, you need to pull the Model
namespace.
Earlier
const Lucid = use('Lucid')
class User extends Lucid {
}
Now
const Model = use('Model')
class User extends Model {
}
The dateFormat
getter has been removed in favor of the alternate approach to handling dates. Read this blog post to learn more about it.
To run model operations inside a transaction, the useTransaction
method was used. In 4.0, you pass the transaction object directly to the save
and create
methods.
Earlier
const trx = yield Database.beginTransaction()
const user = new User()
user.username = 'virk'
user.useTransaction(trx)
yield user.save()
Now
const trx = await Database.beginTransaction()
const user = new User()
user.username = 'virk'
await user.save(trx)
The belongsToMany
method used to receive the pivot table name as part of the method call which has been changed in 4.0.
Earlier
class User extends Lucid {
cars () {
return this.belongsToMany('App/Model/Car', 'my_cars')
}
}
Now
class User extends Model {
cars () {
return this
.belongsToMany('App/Models/Car')
.pivotTable('my_cars')
}
}
The with
method is used to eagerload relationships, and the signature has been changed quite a bit.
Earlier
User
.query()
.with('profiles', 'posts')
Now
User
.query()
.with('profiles')
.with('posts')
Now you have to call with
multiple times to eagerload multiple relations. This is done since the 2nd param
to the with
method is a callback to add query constraints on the relationship.
Earlier
User
.query()
.with('profiles')
.scope('profiles', (builder) => {
builder.where('is_latest', true)
})
Now
User
.query()
.with('profiles', (builder) => {
builder.where('is_latest', true)
})
The attach
method of Belongs To Many relationship has been changed in how it receives the values for pivot tables.
Earlier
await user.cars().attach([1], { current_owner: true })
Now
await user.cars().attach([1], (pivotModel) => {
pivotModel.current_owner = true
})
The new signature makes it easier to add conditional attributes. For example: When calling attach
with 3 cars and wanting to set a different current_owner
attribute for each car. The callback approach makes it easier to do that since the callback is invoked for cars.length
number of times.
The database factories API is tweaked a little to make it more explicit.
Earlier, the create
method could create one or many rows based upon the number passed to it. Now you have to call createMany
to create multiple rows.
It makes sure that the create
method always returns the created model instance and createMany
always returns an array of created model instances.
Earlier
// create one
Factory.model('App/Models/User').create()
// create many
Factory.model('App/Models/User').create(3)
Now
// create one
Factory.model('App/Models/User').create()
// create many
Factory.model('App/Models/User').createMany(3)
The make
method has been changed accordingly, and the makeMany
method has been introduced.
The way to interact with sessions has been changed too. All of the sessions related code has been extracted from core to an individual repo. Now API only servers can easily remove sessions from their app.
Earlier
Route.get('/', function * (request) {
yield request.session.put('username', 'virk')
})
Now
Route.get('/', ({ session }) => {
session.put('username', 'virk')
})
The flash messages signature has also been changed
Earlier
Route.get('/', function * (request) {
yield request.withAll().flash()
// errors
yield request
.withAll()
.andWith({ error: { message: 'Some error' } })
.flash()
})
Now
Route.get('/', ({ session }) => {
session.flashAll()
// errors
await session
.withErrors({ message: 'Some error' })
.flashAll()
})