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.
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.
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 .env | Value 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:
import { Ignite } from '@athenna/core'
const ignite = await new Ignite().load(import.meta.url, {
envPath: './bootstrap/.env.dev' 👈
})
await ignite.httpServer()
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
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
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
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')
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
}
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
}
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
}
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
Path.()
./
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
Path.()
./
config
key to the directories
object:
{
"directories": {
"config": "src/app/config"
}
}
Now when calling the
Path.config()
./src/config
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:
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
Path.('app.ts')
.//app.ts
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.