How to share config across handlers

View Source

config is one value, set once at startup, that every request can read. You need it when your handlers all want the same things: a database pool, a cache, settings you read at boot. It saves you from capturing them in a closure for every handler, using a global, or passing them through the per-request meta map (which is meant for data about one request).

Pass config at startup

Pass config when you start the service. It can be any term, most often a map of handles, and the same value reaches every request:

{ok, Pid} = livery:start_service(#{
    http   => #{port => 8080},
    config => #{db => DbPool, cache => CachePid},
    router => Router
}).

Read it in a handler

Use livery_req:config/1 for the whole value:

list_users(Req) ->
    #{db := Db} = livery_req:config(Req),
    livery_resp:json(200, json:encode(users:all(Db))).

or livery_req:config/2,3 to pull one key out of a map config:

list_users(Req) ->
    Db = livery_req:config(db, Req),
    livery_resp:json(200, json:encode(users:all(Db))).

Middleware sees the same request, so it can read config too:

call(Req, Next, _Opts) ->
    Limiter = livery_req:config(limiter, Req),
    enforce(Limiter, Req, Next).

Use a record, if you prefer

Config can be any term, so a record works too, and gives you a little more structure than a map:

%% -record(app, {db, cache}).
show(Req) ->
    App = livery_req:config(Req),
    livery_resp:json(200, fetch(App#app.db)).

Set config on a single listener

livery:start_listener/2 takes config the same way:

{ok, _} = livery:start_listener(livery_h1, #{
    port => 8080, config => App, stack => Stack, handler => Handler
}).

A config inside one protocol's map on start_service/1 overrides the service-wide one for that listener. This helps when, for example, your TLS listener should use a different pool.

Test with config in the spec

You can still test handlers without a socket: put config in the request spec.

Cap = livery_test_adapter:run([], fun my_app:list_users/1,
    #{method => <<"GET">>, config => #{db => FakeDb}}).

Notes

  • config is service-wide and set at startup: the same value for every request, read-only. Use it for shared handles and settings.
  • meta holds data a middleware writes for one request only, like the authenticated user or a trace id. See Write a custom middleware.

See also