Skip to main content

Configuration

Understand the initial configurations of your project.

Environment configuration

It is often helpful to have different configuration values based on the environment where the application is running. For example, you may wish to use a different storage driver locally than you do on your production server.

To make this a cinch, Athenna utilizes the dotenv Node.js library. In a fresh Athenna installation, the root directory of your application will contain a .env.example file that defines many common environment variables. During the Athenna installation process, this file will automatically be copied to .env and .env.test.

Athenna default .env file contains some common configuration values that may differ based on whether your application is running locally or on a production. These values are then retrieved from various Athenna configuration files within the config directory using Athenna Env() function.

If you are developing with a team, you may wish to continue including a .env.example file with your application. By putting placeholder values in the example configuration file, other developers on your team can clearly see which environment variables are needed to run your application.

tip

Any variable in your .env file can be overridden by external environment variables such as server-level or system-level environment variables. But off course you can turn off this behavior setting the OVERRIDE_ENV=true variable before running your application, if this variable is set to true, all environment variables set in .env will override the externals.

caution

Your .env file should not be committed to your application source control, since each developer/server using your application could require a different environment configuration. Furthermore, this would be a security risk in the event an intruder gains access to your source control repository, since any sensitive credentials would get exposed.

Determining the current environment

Before loading your application's environment variables, Athenna determines if either the --env Artisan flag has been specified or if the APP_ENV environment variable has been externally provided.

If so, Athenna will attempt to load the .env.${env} or .env.${APP_ENV} file if it exists. If the file cannot be found, Athenna will try to find the APP_ENV variable inside the .env file and again try to find the .env.${APP_ENV} file to load. Finally, if the APP_ENV is not present in .env file, Athenna will end up loading the .env file by default.

Running providing --env flag in Artisan:

node artisan serve --env=local

Running providing APP_ENV externally:

APP_ENV=local node artisan serve

Let's check some practical examples. This is the default .env file that comes in Athenna project:

HOST=localhost
PORT=3000

APP_NAME=Athenna
APP_ENV=local
APP_DEBUG=true
APP_URL=http://${HOST}:${PORT}
APP_DOMAIN=${HOST}
APP_DOCUMENTATION=${APP_URL}
APP_SOURCE=https://github.com/AthennaIO

LOG_HTTP=true
LOG_CHANNEL=application

In the .env file above, you can see that we have the APP_ENV=local. This means that if you create a new .env.local file in your project root path, Athenna will load it instead of .env if running your application without a predefined environment.

Get an environment variable value

You can get environment variables using the Env() function:

import { Env } from '@athenna/config'

const defaultValue = 'Athenna'

const appName = Env('APP_NAME', defaultValue)

All environment variables in your .env file and inside process.env object are always interpreted as strings. But when using the Env() function, it will auto cast the value for you. Check the comparison:

Value in .envValue returned by Env function
true (boolean)true (boolean)
"true" (string)true (boolean)
10 (number)10 (number)
"10" (string)10 (number)
10.090909 (float)10.090909 (float)
"10.090909" (string)10.090909 (float)
{"name":"Paulo"} (json){ name: "Paulo" } (Object)
"{"name":"Paulo"}" (json string){ name: "Paulo" } (Object)

Let's see a more practical example of it:

process.env.PORT = '3000'
process.env.APP_DEBUG = 'true'
process.env.APP_JSON = '{"name":"Paulo"}'

console.log(Env('PORT')) // 3000 <- number
console.log(Env('APP_DEBUG')) // true <- boolean
console.log(Env('APP_JSON')) // { name: "Paulo" } <- object

There will certainly have scenarios in your business rule where you explicitly need an environment variable with value true, 10 or {"name":"Paulo"} to be a string. To solve this, you can turn off the auto cast when using the Env() function:

process.env.PORT = '3000'
process.env.APP_DEBUG = 'true'
process.env.APP_JSON = '{"name":"Paulo"}'

const autoCast = false
const defaultValue = undefined

console.log(Env('PORT', defaultValue, autoCast)) // 3000 <- string
console.log(Env('APP_DEBUG', defaultValue, autoCast)) // true <- string
console.log(Env('APP_JSON', defaultValue, autoCast)) // {"name":"Paulo"} <- string

Environment variables can parse other environment variables too. See the example above:

HOST=localhost
PORT=3000

APP_URL=http://${HOST}:${PORT}
console.log(Env('APP_URL')) // "http://localhost:3000"

Custom environment file path

You can also change the name and the path of your .env file. To do that you need to set the new path to Ignite::load() static method:

Path.bin('dev.ts')
import { Ignite } from '@athenna/core'

const ignite = await new Ignite().load(import.meta.url, {
envPath: './bootstrap/.env.dev' 👈
})

await ignite.httpServer()
tip

Always remember that when using relative paths to set something in Athenna, you need to use your project root path as reference, just like in the example above.

Configuration files

All the configuration files for the Athenna framework are stored in the src/config directory. Each option is documented, so feel free to look through the files and get familiar with the options available to you.

Athenna needs almost no additional configuration out of the box. You are free to get started developing! Each option is documented, so feel free to look through the files and get familiar with the options available to you. It contains several options such as locale that you may wish to change, according to your application.

Manipulating configuration values

You may easily access your configuration values using the global Config helper class. The configuration values may be accessed using "dot (.)" syntax, which includes the name of the file and option you wish to access. Let's cover some methods bellow:

Config.get()

The get() method will return the value of your configuration. You can also set a default value as second parameter that will be returned if the configuration option does not exist:

import { Config } from '@athenna/config'

const defaultValue = 'Athenna'
const name = Config.get('app.name', defaultValue)

console.log(name) // MyAppName
tip

You can get all the configuration values using get() method without any key:

console.log(Config.get()) // { app: {...}, http: {...}, ... }

Config.set()

The set() method is very useful to set or change the value of some configuration in runtime:

import { Config } from '@athenna/config'

Config.set('app.name', 'Athenna Framework')

console.log(Config.get('app.name')) // Athenna Framework
warning

The Config.set() method does not change the values in the configuration file, only in runtime. To do that, you will need to use the Config.rewrite() method.

Config.safeSet()

If you are not sure if some configuration value is already set of not, you can use the safeSet() method instead to not overwrite something that was already defined:

import { Config } from '@athenna/config'

console.log(Config.get('app.name')) // MyAppName

Config.safeSet('app.name', 'Athenna Framework')

console.log(Config.get('app.name')) // MyAppName

Config.delete()

The delete() method could be used to delete some configuration value:

import { Config } from '@athenna/config'

Config.delete('app.name')

console.log(Config.get('app.name')) // undefined
warning

Just like Config.set() method, Config.delete() does not change the values in the configuration file, only in runtime. To do that you will need to use the Config.rewrite() method.

Config.rewrite()

The rewrite() method is very useful for rewriting the configuration file. Very useful when you want to programmatically modify the configuration file source code. This method uses the magicast library under the hood to do that:

import { Config } from '@athenna/config'

Config.set('app.name', 'Athenna Framework')

await Config.rewrite('app')
warning

Let's suppose that you want to set a function as a value, you can use builders.functionCall function of magicast library to do that:

import { builders } from 'magicast'
import { Config } from '@athenna/config'

Config.set('app.name', builders.functionCall('Env', ['MY_APP_NAME']))

await Config.rewrite('app')

The example above will produce the following code in

Path.config('app.ts')

./src/config/app.ts

:

export default {
name: Env('MY_APP_NAME')
...
}

Config.is()

The is() method could be used to validate if your configuration value matches some other value:

import { Config } from '@athenna/config'

if (Config.is('app.name', 'Athenna')) {
// do something
}

You can set an array as second parameter to is() method. If any value in the array matches the configuration value, the method will return true:

import { Config } from '@athenna/config'

if (Config.is('app.name', ['Athenna', 'MyAppName'])) {
// do something
}
tip

You can use the isNot() method to do the negated validation.

Config.existsAll()

The existsAll() method could be used to validate if an array of configuration keys exists or not:

import { Config } from '@athenna/config'

if (Config.existsAll(['app.name', 'app.version'])) {
// do something
}
tip

You can use the notExistsAll() method to do the negate validation.

Config.clear()

The clear() method could be used to clear all the configuration values:

import { Config } from '@athenna/config'

Config.clear()

console.log(Config.get()) // {}

Config.load()

The load() method could be used to load some configuration file path:

import { Path } from '@athenna/common'
import { Config } from '@athenna/config'

const testConfig = Path.stubs('config/test.ts') // /path/to/your/project/tests/stubs/config/test.ts
await Config.load(testConfig)

console.log(Config.get('test')) // { ... }

Config.safeLoad()

The safeLoad() method will only load the file path if it is not defined:

import { Path } from '@athenna/common'
import { Config } from '@athenna/config'

const appConfig = Path.stubs('config/app.js') // /path/to/your/project/tests/stubs/config/app.js
await Config.safeLoad(appConfig)

Config.loadAll()

The loadAll method will load all files found inside some configuration path:

import { Path } from '@athenna/common'
import { Config } from '@athenna/config'

const config = Path.stubs('config') // /path/to/your/project/tests/stubs/config
await Config.loadAll(config)

Get configuration with @Value() decorator

Instead of using the Config.get() method to get a configuration value, you can use the @Value() annotation in your classes to automatically add it value to a class property:

import { Value } from '@athenna/config'

export class UserService {
@Value('api.users') 👈
public api: string
}
tip

Just like Config.get() method, you can set a default value when using the @Value() annotation:

@Value('api.users', 'http://localhost:3000/users') 👈

Define my own configuration path

If you are building your own project structure you might want to change the configurations directory from src/config to something else. For this case you can specify to Athenna a different path to what

will resolve.

To specify your application directories to Athenna, you can open the .athennarc.json file and add the directories property to it. The directories property is an object that maps the directory base path that the Path helper will use to resolve your application paths:

import { Path } from '@athenna/common'

console.log(Path.config()) // /path/to/your/project/src/config

All the directories key names follow the Path class methods names. This means that if you want to change what is returned by the

method, you will need to add the config key to the directories object:

{
"directories": {
"config": "src/app/config"
}
}

Now when calling the

Path.config()

./src/config

method, it will return a different path:

import { Path } from '@athenna/common'

console.log(Path.config()) // /path/to/your/project/src/app/config 👈

Athenna always rely on Path class methods to find files and directories that are used internally by the framework, like configuration file, route files, entry points and many others.

Check the directories property documentation section for more information about the directories property. And check the do your own structure documentation section for more information about how to create your own project structure.

Safe loading configuration files

Athenna got multiple types of applications, while using the framework you will notice that some times you could end up igniting your application twice. Let's suppose you are using node artisan serve command to start your application, this command will first ignite your application by Artisan and them by the HTTP server.

This is usually not a problem at all, but depending on how you have created your environment it could become one. To avoid reloading configuration files in these situations, you can set the loadConfigSafe option as true in Ignite::load() static method:

Path.bin('main.ts')
import { Ignite } from '@athenna/core'

const ignite = await new Ignite().load(import.meta.url, {
loadConfigSafe: true, 👈
})

await ignite.httpServer()

Debug mode

The debug option in your

configuration file determines how much information about your application is actually displayed to you and for who is going to consume your application. By default, this option is set to respect the value of the APP_DEBUG environment variable, which is stored in your .env file.

For local development, you should set the APP_DEBUG environment variable to true. In your production environment, this value should always be false, if the variable is set to true in production, you risk exposing sensitive configuration values to your application's end users.