Config
At a certain point in the lifetime of your IHP app you will want to add your own config parameters, e.g. for managing secrets, API keys or external services. This guide explains the best practises for doing that.
Custom Configuration
Dealing with Secrets
Sometimes you want to have a custom configuration flag inside your application.
The recommended way is to declare a custom newtype
in Config/Config.hs
like this:
-- Config.hs
newtype StripePublicKey = StripePublicKey Text
We want our new config parameter to be filled from a STRIPE_PUBLIC_KEY
env variable. Therefore we add this to our Config.hs:
module Config where
newtype StripePublicKey = StripePublicKey Text
config :: ConfigBuilder
config = do
-- ...
stripePublicKey <- StripePublicKey <$> env @Text "STRIPE_PUBLIC_KEY"
option stripePublicKey
Now the app reads the STRIPE_PUBLIC_KEY
env variable at startup and makes it available to the app.
Before we proceed we should add a default value for this in dev mode. Open the start
script and add the following env variables:
# Add this before the `RunDevServer` call at the end of the file
export STRIPE_PUBLIC_KEY="pk_test_..."
# Finally start the dev server
RunDevServer
Using Custom Config Parameters
You can now access the StripePublicKey
parameter by calling getAppConfig @Config.StripePublicKey
:
import qualified Config
action MyAction = do
let (StripePublicKey stripePublicKey) = getAppConfig @Config.StripePublicKey
putStrLn ("Stripe public key: " <> stripePublicKey)
Environment Variables
Reading Environment Variables
Inside Config/Config.hs
you can use env
to read environment variables.
module Config where
config :: ConfigBuilder
config = do
someString <- env @Text "SOME_STRING"
The env
function will raise an error if the env var is not defined.
The env
function can also deal with other common types:
module Config where
config :: ConfigBuilder
config = do
maxRetryCount <- env @Int "MAX_RETRY_COUNT"
byteString <- env @ByteString "SOME_BYTESTRING"
Default Values
Use envOrDefault
to provide a default value for an env var:
module Config where
config :: ConfigBuilder
config = do
redisPort <- envOrDefault @Int 6379 "REDIS_PORT"
Optional Env Variables
When an env variable is optional and has no good default value, use envOrNothing
. It will return Nothing
if the env variable is not set:
module Config where
config :: ConfigBuilder
config = do
redisUrl :: Maybe Text <- envOrNothing "REDIS_URL"
Custom Parser
When you’re dealing with a custom enum type it can be useful to write a custom env parser by implementing an EnvVarReader
:
module Config where
config :: ConfigBuilder
config = do
ipAddrSource :: IPAddrSource <- envOrDefault "IP_ADDR_SOURCE" FromSocket
data IPAddrSource = FromSocket | FromHeader
instance EnvVarReader RequestLogger.IPAddrSource where
envStringToValue "FromHeader" = Right RequestLogger.FromHeader
envStringToValue "FromSocket" = Right RequestLogger.FromSocket
envStringToValue otherwise = Left "Expected 'FromHeader' or 'FromSocket'"
Custom Middleware
IHP provides an “escape-hatch” from the framework with the CustomMiddleware
option.
This can be used to run any WAI middleware after IHP’s middleware stack, allowing for possibilities
such as embedding a Servant or Yesod app into an IHP app, adding GZIP compression, or any other
number of possibilities. See wai-extra for examples
of WAI middleware that could be added.
The following example sets up a custom middleware that infers the real IP using X-Forwarded-For
and adds a custom header for every request.
module Config where
import Network.Wai.Middleware.AddHeaders (addHeaders)
import Network.Wai.Middleware.RealIp (realIp)
config :: ConfigBuilder
config = do
option $ CustomMiddleware $ addHeaders [("X-My-Header", "Custom WAI Middleware!")] . realIp