Skip to main content
version 1.0.0

Facades

Introduction

Throughout the Athenna documentation, you will see examples of code that interacts with Athenna features via "facades". Facades provide a "static" interface to class that are available in the application's service container. Athenna ships with many facades which provide access to almost all of Athenna features.

Athenna facades serve as "static proxies" to underlying classes in the service container, providing the benefit of a terse, expressive syntax while maintaining more testability and flexibility than traditional static methods. It's perfectly fine if you don't totally understand how facades work under the hood - just go with the flow and continue learning about Athenna.

All of Athenna facades are defined in their own packages. So, we can easily access a facade like so:

import { Log } from '@athenna/logger' // Log Facade
import { Route } from '@athenna/http' // Route Facade

Route.get('/welcome', function ({ response }) {
Log.channel('simple').success('New request received 😝')

return response.status(200).send({ hello: 'world' })
})

Throughout the Athenna documentation, many of the examples will use facades to demonstrate various features of the framework.

Helper functions

To complement facades, Athenna offers a variety of global "helper functions" that make it even easier to interact with common Athenna features. Some common helper functions you may interact with are Env, Config, Path, and more. Each helper function offered by Athenna is documented with their corresponding feature; however, a complete list is available within the dedicated helper documentation.

When to use facades

Facades have many benefits. They provide a terse, memorable syntax that allows you to use Athenna features without remembering long class names that must be injected or configured manually.

However, some care must be taken when using facades. The primary danger of facades is class "scope creep". Since facades are so easy to use and do not require injection, it can be easy to let your classes continue to grow and use many facades in a single class. Using dependency injection, this potential is mitigated by the visual feedback a large constructor gives you that your class is growing too large. So, when using facades, pay special attention to the size of your class so that its scope of responsibility stays narrow. If your class is getting too large, consider splitting it into multiple smaller classes.

How facades work

In an Athenna application, a facade is a class that provides access to an object from the container. The machinery that makes this work is in the Facade class. Athenna facades, and any custom facades you create, will extend the base Facade class.

The Facade base class makes use of the Proxy API to defer calls from your facade to an object resolved from the container. In the example below, a call is made to the Athenna log system. By glancing at this code, one might assume that the static success method is being called on the Log class:

import { Log } from '@athenna/logger'

export class WelcomeController {
/**
* Show the welcome payload.
*
* @param {import('@athenna/http').ContextContract} ctx
* @return any
*/
async show({ response }) {
Log.success('Welcome to Athenna 😝')

return response.status(200).send({ hello: 'world' })
}
}

Notice that near the top of the file we are importing the Log facade. This facade serves as a proxy for accessing the underlying implementation of the Logger class from Athenna service container. Any calls we make using the facade will be passed to the underlying instance of Athenna logger class.

If we look at that Log, you'll see that Log is just a constant that points to the alias Athenna/Core/Logger that is registered inside the service container:

import { Facade } from '@athenna/ioc'

/** @type {Facade & import('#src/index').Logger} **/
export const Log = Facade.createFor('Athenna/Core/Logger')

This method's job is to return the name of a service container binding. When a user references any static method on the Log facade, Athenna resolves the Athenna/Core/Logger binding from the service container and runs the requested method (in this case, success) against that object.

Facade class reference

Below you will find every facade and its underlying class. This is a useful tool for quickly digging into the API documentation for a given facade root. The service container binding key is also included where applicable.

FacadeReference classService container binding alias
LogLoggerAthenna/Core/Logger
ServerHttpAthenna/Core/HttpServer
RouteRouterAthenna/Core/HttpRoute
ArtisanArtisanAthenna/Core/Artisan
DatabaseDatabaseAthenna/Core/Database

Writing facades

First you need to create a new service provider to resolve your dependency inside the service container. Just run node artisan make:provider HelperProvider and register your binding:

import { String } from '#app/Helpers/String'
import { ServiceProvider } from '@athenna/ioc'

export class HelperProvider extends ServiceProvider {
/**
* Register any application services.
*
* @return {void}
*/
register() {}

/**
* Bootstrap any application services.
*
* @return {void}
*/
boot() {
this.container.bind('App/Helpers/String', String)
}
}
caution

If you have not used node artisan make:provider command, you will need to register your HelperProvider inside config/app.js file inside the providers array.

The Artisan can generate a new Facade via the make:facade command:

node artisan make:facade String

The Facade file will be generated inside providers/Facades folder. Open this file and change the alias to your provider alias:

import { Facade } from '@athenna/ioc'

/** @type {Facade & import('#app/Helpers/String').String} */
export const String = Facade.createFor('App/Helpers/String')

Now we can start using our brand new String facade:

import { Route } from '@athenna/http' // Route Facade
import { String } from '#providers/Facades/String' // String Facade

Route.get('/welcome', function ({ response }) {
return response.status(200).send({ hello: String.toPascalCase('world') })
})