Skip to main content

Service Providers

Understand the purpose and how to use the Athenna 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, retry strategies for your http requests, booting your FakeApi. Service providers are the central place to configure your application.

If you open the .athennarc.json 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, controllers 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 the application lifecycle documentation section.

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. We will check lately why this behavior. To create a new provider, use the following command:

node artisan make:provider AppProvider

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 piece of functionality within the register method. Otherwise, you may accidentally use a service 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 { AppHelper } from '#app/helpers/AppHelper'

export default class AppProvider extends ServiceProvider {
public register() {
this.container.singleton('App/Helpers/AppHelper', AppHelper)
}
}

This service provider only defines a register method, and uses that method to define an implementation of AppHelper in the service container. If you're not yet familiar with Athenna service container, check out its documentation.

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 { AppHelper } from '#app/helpers/AppHelper'

export default class AppProvider extends ServiceProvider {
public boot() {
const appService = this.container.safeUse('App/Services/AppService')
const appHelper = new AppHelper(appService)

this.container.instance('App/Helpers/AppHelper', appHelper)
}
}

The shutdown method

The shutdown method will be called when the application is going down for some reason. This method is extremely useful for graceful shutdown the application, and it's used in providers such as HttpServerProvider and DatabaseProvider:

import { ServiceProvider } from '@athenna/ioc'

export default class AppProvider extends ServiceProvider {
public async shutdown() {
const appHelper = this.container.use('App/Helpers/AppHelper')

if (!appHelper) {
return
}

await appHelper.close()
}
}

Registering providers

All service providers are registered in the .athennarc.json 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.

If you are using the make:provider command to create providers, Athenna will automatically register the provider for you in this array, but if you might need to register it manually, just add the path to it to the array:

{
"providers": [
// Other service providers...

"#providers/AppProvider"
]
}

Choosing applications

In some cases you want that your provider only runs for a determined type of Athenna applications. For example, I have a MockedDbProvider that connects 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 MockedDbProvider when running Artisan application and commands?

To solve this problem you can use the environment getter to choose the applications that could run this provider:

import { ServiceProvider } from '@athenna/ioc'

export default class MockedDbProvider extends ServiceProvider {
public get environment() {
return ['http', 'repl']
}
}

Now when running Artisan application and commands, MockedDbProvider will be ignored.

The following environments are available by default in Athenna at this moment:

  • http
  • repl
  • console

You could also create your own environments. In your Path.bootstrap('main.ts') file you can add an environments option when calling Ignite.load() method:

import { Ignite } from '@athenna/core'

const ignite = await new Ignite().load(import.meta.url, {
environments: ['myEnv']
})

await ignite.httpServer()

All of your environments will be merged with Athenna default ones when running your application. This means that in the example above, when running your http server, the providers bootstrapped will be only the ones that got the environments getter returning ['*'] and ['myEnv', 'http'].