Service Providers
Introduction
Service providers are the central place of all Athenna application bootstrapping. Your own application, as well as all of Athenna core services, are bootstrapped via service providers.
But, what do we mean by "bootstrapped"? In general, we mean registering things, including registering service container bindings, event listeners, middleware, and even routes. Service providers are the central place to configure your application.
If you open the config/app.js
file included with Athenna, you will see a providers array. These are all the service provider
classes that will be loaded for your application. By default, a set of Athenna core service providers are listed in this array.
These providers bootstrap the core Athenna components, such as the http, route, logger, services and others.
In this overview, you will learn how to write your own service providers and register them with your Athenna application.
tip
If you would like to learn more about how Athenna works internally, check out our documentation on the Athenna request lifecycle.
Writing service providers
All service providers extend the ServiceProvider
class. Most service providers contain a register
and a boot
method.
Within the register
method, you should only bind things into the service container. You should never attempt to register
any event listeners, routes, or any other piece of functionality within the register method.
The Artisan can generate a new provider via the make:provider
command:
node artisan make:provider MyAppProvider
The register method
As mentioned previously, within the register
method, you should only bind things into the service container. You should
never attempt to register any event listeners, routes, or any other piece of functionality within the register method.
Otherwise, you may accidentally use a service that is provided by a service provider which has not loaded yet.
Let's take a look at a basic service provider. Within any of your service provider methods, you always have access to
the this.container
property which provides access to the service container:
import { ServiceProvider } from '@athenna/ioc'
import { MyAppService } from '#app/Utils/MyAppService'
export class MyAppProvider extends ServiceProvider {
/**
* Register any application services.
*
* @return {Promise<void>}
*/
register() {
this.container.singleton('MyApp/Service', MyAppService)
}
}
This service provider only defines a register
method, and uses that method to define an implementation of MyAppService
in the service container. If you're not yet familiar with Athenna service container, check out its documentation.
The bindings and singletons properties
If your service provider registers many simple bindings, you may wish to use the bindings
and singletons
properties instead
of manually registering each container binding. When the service provider is loaded by the framework, it will automatically
check for these properties and register their bindings:
import { ServiceProvider } from '@athenna/ioc'
import { MyAppService } from '#app/Utils/MyAppService'
import { MyAppResource } from '#app/Utils/MyAppResource'
import { MyAppRepository } from '#app/Utils/MyAppRepository'
export class MyAppProvider extends ServiceProvider {
/**
* All the container bindings that should be registered.
*/
get bindings() {
return {
'MyApp/Service': MyAppService
}
}
/**
* All the container instances that should be registered.
*/
get instances() {
return {
'MyApp/Resource': new MyAppResource()
}
}
/**
* All the container singletons that should be registered.
*/
get singletons() {
return {
'MyApp/Repository': MyAppRepository
}
}
}
The boot method
So, what if we need to register a dependency that needs another dependency within our service provider? This should be
done within the boot
method. This method is called after all other service providers have been registered, meaning you
have access to all other services that have been registered by the framework:
import { ServiceProvider } from '@athenna/ioc'
import { MyAppService } from '#app/Utils/MyAppService'
export class MyAppProvider extends ServiceProvider {
/**
* Bootstrap any application services.
*
* @return {Promise<void>}
*/
boot() {
/** @type {import('#app/Utils/MyAppRepository').MyAppRepository} */
const myAppRepository = this.container.safeUse('MyApp/Repository')
this.container.instance('MyApp/Service', new MyAppService(myAppRepository))
}
}
The shutdown method
The shutdown method will be called when the application is going down for some reason. This method is extremely useful for gracefully shutdown
the application, and it's used in providers such as HttpServerProvider
and DatabaseProvider
:
import { ServiceProvider } from '@athenna/ioc'
export class MyAppProvider extends ServiceProvider {
/**
* Shutdown any application services.
*
* @return {Promise<void>}
*/
async shutdown() {
/** @type {import('#app/Utils/MyAppRepository').MyAppRepository} */
const MyAppService = this.container.use('MyApp/Service')
if (!MyAppService) {
return
}
await MyAppService.close()
}
}
Registering providers
All service providers are registered in the config/app.js
configuration file. This file contains a providers array where
you can list the class names of your service providers. By default, a set of Athenna core service providers are listed
in this array. These providers bootstrap the core Athenna components, such as the http, route, services and others.
To register your provider, add it to the array:
providers: [
// Other service providers
import('#providers/MyAppProvider'),
],
Choosing applications
In some cases you want that your provider only run for determined type of Athenna applications. For example, I have a TestingDatabaseProvider
that connect with some database, if I don't have an Artisan command
that do some work inside
this database, why I would need to run this TestingDatabaseProvider
on application bootstrap?
To solve this problem you can use the bootstrapIn
getter to choose the applications that could run this provider:
/**
* Set where the type of application where this provider can
* be registered or not.
*
* @return {string[]}
*/
get bootstrapIn() {
return ['http', 'worker']
}
Now in bootstrap/main
file we need to define the ATHENNA_APPLICATIONS
environment variable before the application ignite:
import { Ignite } from '@athenna/core'
import { install } from 'source-map-support'
async function main() {
install()
process.env.ATHENNA_APPLICATIONS = 'http,worker'
const application = await new Ignite().fire()
await application.bootWorker()
await application.bootHttpServer()
}
main().catch()
Now when running node artisan
commands the TestingDatabaseProvider
will not be ignited!