Extending the framework
The architecture of AdonisJS makes it very easy to extend the framework. We dogfood framework's core APIs to build an ecosystem of first-party packages.
In this guide, we will explore different APIs you can use to extend the framework through a package or within your application codebase.
Macros and getters
Macros and getters offer an API to add properties to the prototype of a class. You can think of them as Syntactic sugar for Object.defineProperty
. Under the hood, we use macroable package, and you can refer to its README for an in-depth technical explanation.
Since macros and getters are added at runtime, you will have to inform TypeScript about the type information for the added property using declaration merging.
You can write the code for adding macros inside a dedicated file (like the extensions.ts
) and import it inside the service provider's boot
method.
export default class AppProvider {
async boot() {
await import('../src/extensions.js')
}
}
In the following example, we add the wantsJSON
method to the Request class and define its types simultaneously.
import { Request } from '@adonisjs/core/http'
Request.macro('wantsJSON', function (this: Request) {
const firstType = this.types()[0]
if (!firstType) {
return false
}
return firstType.includes('/json') || firstType.includes('+json')
})
declare module '@adonisjs/core/http' {
interface Request {
wantsJSON(): boolean
}
}
- The module path during the
declare module
call must be the same as the path you use to import the class. - The
interface
name must be the same as the class name to which you add the macro or the getter.
Getters
Getters are lazily evaluated properties added to a class. You can add a getter using the Class.getter
method. The first argument is the getter name, and the second argument is the callback function to compute the property value.
Getter callbacks cannot be async because getters in JavaScript cannot be asynchronous.
import { Request } from '@adonisjs/core/http'
Request.getter('hasRequestId', function (this: Request) {
return this.header('x-request-id')
})
// you can use the property as follows.
if (ctx.request.hasRequestId) {
}
Getters can be a singleton, meaning the function to compute the getter value will be called once, and the return value will be cached for an instance of the class.
const isSingleton = true
Request.getter('hasRequestId', function (this: Request) {
return this.header('x-request-id')
}, isSingleton)
Macroable classes
Following is the list of classes that can be extended using Macros and getters.
Class | Import path |
---|---|
Application | @adonisjs/core/app |
Request | @adonisjs/core/http |
Response | @adonisjs/core/http |
HttpContext | @adonisjs/core/http |
Route | @adonisjs/core/http |
RouteGroup | @adonisjs/core/http |
RouteResource | @adonisjs/core/http |
BriskRoute | @adonisjs/core/http |
ExceptionHandler | @adonisjs/core/http |
MultipartFile | @adonisjs/core/bodyparser |
Extending modules
Most of the AdonisJS modules provide extensible APIs to register custom implementations. Following is an aggregated list of the same.