Tool

Expose PostgreSQL databases as instant REST APIs

Bier is an alpha-stage Elixir library that generates a PostgREST-compatible REST API on the fly from PostgreSQL introspection.

Works with postgrespostgresql

91
Spark score
out of 100
Updated 22 days ago
Source checked Sep 10, 2026
Version 0.2.0

Add to Favorites

Why it matters

Automatically generate and serve a complete RESTful API from any PostgreSQL database by introspecting its schema at runtime, eliminating the need to write controllers, routes, or schema definitions while maintaining security through role-based access control and JWT authentication.

Outcomes

What it gets done

01

Introspect PostgreSQL tables, views, functions, and foreign keys to build API endpoints on the fly

02

Execute filtered queries, inserts, updates, and deletes through HTTP with automatic JSON serialization

03

Authenticate requests using JWT tokens and enforce database role-based permissions

04

Call stored database functions via RPC endpoints with parameter binding and result formatting

Source

Get it from source

Spark does not host a copy of it.

Open source

Reports

Agent outcome reports

No reports yet

Overview

Bier

Bier is an alpha-stage Elixir library that generates a RESTful API on the fly from PostgreSQL introspection, closely tracking the request/response behavior of PostgREST. It runs as a supervised instance embedded in an Elixir app, or standalone via Docker and PGRST_* environment variables, serving filtered, paginated reads, mutations, and database-function calls over plain HTTP with no controllers to write. Use it when you want a PostgREST-style auto-generated API on an Elixir stack and are comfortable with alpha-quality software. It deliberately diverges from PostgREST on a few documented behaviors, so verify conformance against your own use case first.

What it does

Bier is an alpha-stage Elixir library that generates a RESTful API on the fly from PostgreSQL introspection - point it at a database and it inspects tables, views, functions, and foreign keys, then serves them over HTTP with no controllers, route files, or schema definitions to write. It is heavily inspired by PostgREST and tracks PostgREST's request/response behavior closely. Each Bier instance is a supervision tree the host application starts: it opens a Postgrex connection pool, introspects the configured schemas, builds a Plug.Router module at runtime, and starts a Bandit web server. Every incoming request resolves to a {schema, relation} pair and compiles into one parameterized SQL statement whose result set is rendered as JSON, CSV, or GeoJSON (via PostGIS) depending on content negotiation.

When to use - and when NOT to

Use Bier when you want a REST API over a PostgreSQL database without writing controllers or endpoints by hand, especially if you already know PostgREST's conventions: config keys, JWT auth, and the query grammar are named after PostgREST's equivalents, and Bier can run standalone from PGRST_* environment variables as a drop-in for the settings it implements. It ships with a 805-case conformance suite derived from PostgREST v16.0, and all 800 active cases currently pass. It is explicitly alpha - "expect bugs and possibly security flaws" - and not ready for production use. It is also not a byte-for-byte PostgREST clone by design: it deliberately diverges on a short list of documented behaviors, such as naming its own Server: bier/<version> header rather than impersonating PostgREST, being stricter about malformed select parameters, and handling the CORS Vary header differently - so treat a "PostgREST-compatible" claim as request/response conformance, not an identical implementation.

Inputs and outputs

Add a Bier child to a supervision tree with a name, router (port/scheme), database connection details, and db_schemas; each named instance gets its own config, connection pool, and web server, so multiple instances can run side by side. Once running, the database is reachable over plain HTTP - filtered, paginated GET requests, inserts that return the created row, and calls to database functions under /rpc/<fn>. Options are validated by a NimbleOptions schema and can be set via application config instead of start_link/1; notable ones include db_anon_role for unauthenticated requests, jwt_secret/jwt_aud for token verification (an HMAC string or a JWK/JWK Set), openapi_version (Bier can additionally emit OpenAPI 3.0.3, unlike PostgREST), and CORS/Server-Timing/log-level settings. After a schema change, NOTIFY pgrst, 'reload schema' (or Bier.reload_schema_cache/1) refreshes the introspected snapshot without restarting; a failed reload keeps the previous snapshot serving.

Integrations

Bier runs on Bandit and Plug, uses Postgrex/DBConnection for PostgreSQL access, JOSE for JWT verification, and telemetry for observability events; the response JSON encoder is pluggable (the stdlib JSON module by default, or Jason). It can also run standalone via Docker or a mix release, driven entirely by PGRST_* environment variables (BIER_STANDALONE=1 boots an instance from the environment inside the release). A published k6 benchmark against PostgREST v16.1, run head-to-head on the same PostgreSQL instance across four scenarios, has Bier sustaining 2.7-4.2x PostgREST's peak throughput with latency at parity or better and a tighter p99 tail (under 15ms in every scenario tested, versus tens of milliseconds for PostgREST's read paths) - the authors note this is one machine's snapshot, not a universal benchmark claim.

{Bier,
 name: MyApp.Bier,
 router: [port: 4040, scheme: :http],
 database: "my_app_dev",
 username: "postgres",
 password: "postgres",
 db_schemas: ["api"]}

Who it's for

Elixir teams who want a PostgREST-style auto-generated API without running a separate Haskell service, and who are comfortable with alpha-quality software while it matures - the conformance suite and published benchmarks exist specifically so early adopters can judge behavior and performance against the PostgREST baseline before committing. Its repository links a LICENSE file, though the README text itself doesn't spell out which license family it is.

Source README

Bier

CI
Hex.pm
Documentation
License

Alpha. Bier is in its first stage. Expect bugs and possibly security
flaws - it is not ready for production use.

Bier is an Elixir library that serves a RESTful API generated on the fly from
PostgreSQL introspection: point it at a database and it inspects the tables,
views, functions, and foreign keys and exposes them over HTTP - no controllers,
no route files, no schema definitions to write. It is heavily inspired by
PostgREST, and tracks PostgREST's request/response behavior closely (see
Conformance).

How it works, in one paragraph

Each Bier instance is a supervision tree the host application starts. On boot it
opens a Postgrex connection pool, introspects the configured schemas, builds
a Plug.Router module at runtime, and starts a Bandit web server with it.
Every incoming request is resolved to a {schema, relation} at request time and
compiled into one parameterized SQL statement that returns its result set as
JSON, which is then rendered in the negotiated media type.

Installation

Add bier to your dependencies:

def deps do
  [
    {:bier, "~> 0.2"}
  ]
end

To track unreleased work on main, use a git dependency instead:

def deps do
  [
    {:bier, github: "milmazz/bier"}
  ]
end

Requires Elixir ~> 1.18 (developed against Elixir 1.20 / OTP 29) and a
reachable PostgreSQL instance. Bier pulls in Bandit, Plug, Postgrex,
DBConnection, NimbleOptions, JOSE (JWT verification), and
telemetry as runtime dependencies.

Usage

Add a Bier child to your application's supervision tree. Each child is one
named instance with its own config, connection pool, and web server;
multiple instances coexist by passing distinct :name values.

children = [
  {Bier,
   name: MyApp.Bier,
   router: [port: 4040, scheme: :http],
   database: "my_app_dev",
   username: "postgres",
   password: "postgres",
   db_schemas: ["api"]}
]

Supervisor.start_link(children, strategy: :one_for_one)

Once it is up, the database is reachable over HTTP, e.g.:

# read rows, filter, select columns, order, paginate
curl "http://localhost:4040/items?select=id,name&age=gte.18&order=name.asc&limit=10"

# insert and get the row back
curl -X POST "http://localhost:4040/items" \
  -H "Content-Type: application/json" \
  -H "Prefer: return=representation" \
  -d '{"name": "Ada"}'

# call a database function
curl "http://localhost:4040/rpc/add?a=1&b=2"

Documentation

New to Bier? Start with the tutorials, then reach for the reference guides.

Tutorials (learn by building a brewery catalog API)

  • Getting Started - create the database, boot Bier, and make your first requests.
  • Authentication - add roles and JWTs so only members can post.
  • Realtime - push new rows to the browser with a trigger and the SSE endpoint.

Reference

  • API reference - reading, filtering, ordering, pagination, embedding, mutations, RPC, time zones, and content negotiation.
  • Configuration - every option, the PGRST_* environment variables, and standalone/Docker/CLI operation.
  • Observability - telemetry events, Server-Timing, health endpoints, and the error envelope.
  • Realtime events - the SSE endpoint: channels, auth, delivery semantics, telemetry, and - with events_publication - a WAL change feed with typed row events and Last-Event-ID resume.
  • Injection safety - what is bound vs. escaped in the generated SQL, and why.

Configuration

Options are validated by a NimbleOptions schema. Their defaults are sourced
from application env, so you can also set them under config :bier, … instead
of passing them to start_link/1. The main keys (named after their PostgREST
equivalents):

Key Default Purpose
name Bier Instance name; also the registry key and <name>.Router module.
router [port: 4040, scheme: :http] Bandit web-endpoint options.
hostname / port / database / username / password localhost / 5432 / bier / - / - Postgres connection.
pool_size 10 Per-instance Postgrex pool size.
db_prepared_statements true Cache hot-path statements as prepared statements per connection; disable behind transaction-mode poolers.
db_schemas ["public"] Ordered list of exposed schemas; the first is the default.
db_anon_role nil Role assumed for unauthenticated requests.
db_extra_search_path ["public"] Extra schemas appended to the search path.
db_max_rows nil Cap on rows returned per request.
db_tx_end :commit End each request's transaction with :commit or :rollback.
db_pre_request nil Function run inside every request transaction before the main query.
jwt_secret / jwt_aud nil JWT verification secret (an HMAC string, or a JWK/JWK Set for RS/ES/PS/EdDSA) and expected audience.
jwt_role_claim_key "$.role" RFC 9535 JSON Path to the role inside the JWT claims.
client_error_verbosity "verbose" Error envelope shape; "minimal" drops details/hint.
url_use_legacy_target_names true Allow filters/orders to address an aliased embed by its relation name (deprecated, warns).
server_cors_allowed_origins nil Comma-separated CORS allow-list.
server_timing_enabled false Emit a Server-Timing header.
server_trace_header nil Request header (e.g. X-Request-Id) echoed on the response.
log_level :error Access-log verbosity.
openapi_mode "follow-privileges" How the root OpenAPI document is served; under follow-privileges, per-role privilege filtering is cached and refreshes on schema-cache reload.
openapi_version "2.0" OpenAPI document version; "3.0" emits OpenAPI 3.0.3 (a Bier extension; PostgREST/postgrest#932).
openapi_security_active false Advertise JWT security definitions in the OpenAPI document.

The configuration guide documents every option

  • type, default, PGRST_* variable, and the validators that can reject a
    configuration at boot.

Pluggable JSON

Bier.json_library/0 returns the configured encoder (the stdlib JSON module by
default, which requires Elixir 1.18+). Override it with:

config :bier, :json_library, Jason

Schema-cache reload

Bier introspects the database at boot and serves from that snapshot. After a
DDL change (new table, column, FK), reload the cache without restarting -
exactly like PostgREST:

NOTIFY pgrst, 'reload schema';

Every instance listens on the db_channel channel (default "pgrst") with a
dedicated connection; set db_channel_enabled: false to opt out and save the
connection. From Elixir, Bier.reload_schema_cache(MyApp.Bier) does the same
on demand (PostgREST's SIGUSR1 equivalent). A failed reload keeps the
previous snapshot serving. 'reload config' is accepted and logged, but a
no-op: the host application owns Bier's configuration.

To reload automatically on every DDL change, install PostgREST's event
trigger:

CREATE OR REPLACE FUNCTION public.pgrst_watch() RETURNS event_trigger
  LANGUAGE plpgsql
  AS $$
BEGIN
  NOTIFY pgrst, 'reload schema';
END;
$$;

CREATE EVENT TRIGGER pgrst_watch
  ON ddl_command_end
  EXECUTE PROCEDURE public.pgrst_watch();

Running standalone

Bier is primarily a library you embed (see Usage), but it can also run
as a standalone server - no host application required - configured entirely
from PostgREST-compatible PGRST_* environment variables. This is a config-level
drop-in for PostgREST for the settings Bier implements.

Docker

docker build -t bier .

docker run --rm -p 3000:3000 \
  -e PGRST_DB_URI="postgresql://authenticator:secret@db:5432/app" \
  -e PGRST_DB_SCHEMAS="api" \
  -e PGRST_DB_ANON_ROLE="web_anon" \
  bier

The image runs bin/bier start, which boots one instance bound to
PGRST_SERVER_PORT (default 3000). A fatal config problem (e.g. a JWT secret
shorter than 32 characters) is printed to stderr and aborts startup.

Release

MIX_ENV=prod mix release builds a self-contained release under
_build/prod/rel/bier:

MIX_ENV=prod mix release

BIER_STANDALONE=1 \
PGRST_DB_URI="postgresql://authenticator:secret@localhost:5432/app" \
PGRST_DB_SCHEMAS="api" \
PGRST_DB_ANON_ROLE="web_anon" \
_build/prod/rel/bier/bin/bier start

BIER_STANDALONE=1 is what tells Bier.Application to boot an instance from the
environment; it is baked into the Docker image. Without it (the default), the
application starts only its registry, so embedding Bier in a host app is
unaffected.

Inspecting configuration

The bier escript (mix escript.build) resolves and prints the effective
config without starting a server - useful for debugging a deployment's env:

PGRST_DB_SCHEMAS=api ./bier --dump-config
./bier --help

Supported PGRST_* keys mirror the Configuration table
(PGRST_DB_URI, PGRST_DB_SCHEMAS, PGRST_SERVER_PORT, PGRST_JWT_SECRET,
PGRST_LOG_LEVEL, …) plus their deprecated PostgREST aliases. A handful of
PostgREST keys are accepted and echoed without having an effect, so an existing
PostgREST config can be pointed at Bier unedited; anything outside that set is
rejected. The configuration guide has the full
list, along with the in-database (ALTER ROLE … SET pgrst.*) configuration
source, which outranks both the environment and the config file.

Architecture

There are two supervisors with different jobs. Bier.Application (the OTP
mod:) starts only node-wide infrastructure: Bier.Registry, the process
registry every Bier instance registers through, and Bier.Events.Registry, the
pub/sub registry behind the SSE endpoint. It does not start a web server -
except under BIER_STANDALONE=1, where it additionally boots one instance from
the environment (see Running standalone). Bier itself
is the per-instance Supervisor the host application starts; each instance
owns its config, its Postgrex pool, a DynamicSupervisor, and a dynamically
generated router module.

Boot flow

sequenceDiagram
    participant A as MyApp.Application
    participant B as Bier (Supervisor)
    participant C as Bier.Config
    participant P as Postgrex pool
    participant E as Bier.HttpServerStarter
    participant I as Bier.Introspection
    participant F as Bier.RouterBuilder
    participant G as Bandit
    A->>+B: start_link(name:, router:, …)
    B->>+C: new!/2 (validate opts, defaults from app env)
    C->>-B: %Bier.Config{}
    B->>P: start per-instance pool (via Bier.Registry)
    B->>+E: start_link(config)
    E->>+I: run / functions / media_handlers(pool, db_schemas)
    I->>P: query pg_catalog
    I->>-E: relations, functions, media handlers
    E->>E: stash introspection in :persistent_term
    E->>+F: build(config, relations)
    F->>-E: <name>.Router module
    E->>+G: start Bandit (plug: Router) under the DynamicSupervisor
    G->>-E: listening
    E->>-B: {:ok, state}
    B->>-A: ready

Bier.RouterBuilder.build/2 creates the router with Module.create/3 at runtime,
named <name>.Router. It is a thin catch-all: every request flows through a
fixed plug pipeline (:matchassign_instanceBier.Plugs.Cors
Bier.Plugs.VaryBier.Plugs.WarningBier.Plugs.Observability
Bier.Plugs.ReadBody:dispatch) and is then
forwarded to Bier.Plugs.ActionController. Because the router is regenerated on
every boot it is not checked in, and grepping for routes will not find them - edit
the quoted block in RouterBuilder instead.

After HttpServerStarter, the supervisor also starts Bier.SchemaCacheListener
(unless db_channel_enabled: false), which LISTENs on db_channel and swaps
the Bier.SchemaCache snapshot on NOTIFY … 'reload schema'.

Request flow

sequenceDiagram
    participant C as Client
    participant G as Bandit
    participant R as <name>.Router
    participant AC as Bier.Plugs.ActionController
    participant AU as Bier.Auth
    participant QP as Bier.QueryParser
    participant QE as Bier.QueryExecutor
    participant RN as Bier.Response / Render
    participant FC as Bier.Plugs.FallbackController
    C->>+G: HTTP request
    G->>+R: catch-all match
    R->>R: :match → assign_instance → Cors → Vary → Warning → Observability → ReadBody → :dispatch
    R->>+AC: call/2
    AC->>AC: resolve {schema, relation} from path + Accept/Content-Profile
    opt schema requires auth
        AC->>+AU: resolve (JWT verify, SET LOCAL ROLE, request.* GUCs)
        AU->>-AC: auth context
    end
    alt GET / HEAD
        AC->>+QP: parse_request(query_string)
        QP->>-AC: plan (select / filter / order / limit / embed)
        AC->>+QE: run(pool, relation, plan) → one SQL → JSON
        QE->>-AC: {body, count}
        AC->>+RN: render (JSON / CSV / singular / nulls-stripped, Content-Range)
        RN->>-AC: conn
    else POST / PATCH / PUT / DELETE
        AC->>AC: Bier.Mutation.handle (INSERT/UPDATE/DELETE/upsert RETURNING)
    else /rpc/<fn>
        AC->>AC: Bier.Rpc.dispatch (scalar / setof / composite / void)
    end
    alt success
        AC->>-G: %Plug.Conn{}
    else error
        AC->>FC: FallbackController.call (PGRST error envelope)
        FC->>G: %Plug.Conn{}
    end
    G->>-C: response

ActionController resolves the target and method, runs the read/mutation/RPC
path, and lets any non-Plug.Conn return value fall through to
Bier.Plugs.FallbackController, which maps internal reasons and Postgres
SQLSTATEs to HTTP statuses and PostgREST's {code, message, details, hint}
error envelope.

Every request runs as one parameterized SQL statement; the injection-safety
model (what is bound vs. escaped, and why) is in docs/injection_safety.md.

Content negotiation

Responses are rendered in the client's negotiated media type: application/json
(default), text/csv, and application/geo+json. geo+json is offered on
relation reads, on mutations sent with Prefer: return=representation, and on
/rpc/* calls, whenever the PostGIS extension is installed (a target relation
without a geometry column errors with SQLSTATE 22023, mirroring PostgREST).
ST_AsGeoJSON is emitted unqualified and resolves via the session
search_path (matching PostgREST) - a PostGIS installed outside the
search path fails at execution.

Advertised server version

Every response carries Server: bier/<version> - Bier's own mix.exs version
(Bier.version/0), which is also what the OpenAPI document reports as
info.version. It is not configurable: the header is written from a
before_send callback in Bier.Plugs.Observability, so it also reaches the
responses the error funnel builds.

The dialect - which PostgREST release this build is wire-conformant with -
is a separate question, answered by the OpenAPI document's externalDocs URL
(https://postgrest.org/en/v16/…) and by Bier.postgrest_version/0. That
split is deliberate; see the divergence note below.

The query parser

Bier.QueryParser is a generated, dependency-free module built from its
lib/bier/query_parser.ex.exs template via mix gen.parsers (which runs
mix nimble_parsec.compile). nimble_parsec is a dev/test-only dependency -
the shipped code does not depend on it at runtime. Edit the .ex.exs template
and regenerate; never edit the generated .ex directly.

Conformance

Bier reproduces the request/response behavior of PostgREST v16.0, and is
developed against a frozen conformance suite derived from it: 805 cases across
17 areas - URL grammar, operators, select/embedding, filters, ordering,
pagination, representations, mutations, RPC, auth, errors, headers, content
negotiation, OpenAPI, config, observability, and domain representations.
PostgREST is the ground truth - each case cites the exact upstream source line,
and a difference from upstream is treated as a Bier bug.

All 800 active cases pass. Five are excluded: three assert the HTTP reason
phrase, which the test client cannot read (#42), and two pin upstream
behavior Bier deliberately answers differently (see below).

The suite and the behavior models it is built from live under spec/
in the repository, and docs/CONFORMANCE_IMPL.md documents
how it is wired. Neither ships in the package.

Deliberate divergences from PostgREST

PostgREST is the ground truth, and every divergence from it is a bug - with the
short list of exceptions below, where matching upstream would mean reproducing
a defect or misrepresenting what this server is. Each is recorded here so it is
not mistaken for drift.

Server: bier/<version>. Upstream sets Server: postgrest/<version>, and
conformance case 1771 pins that prefix - the one case Bier is knowingly
exempted from. A Server header names the software that built the response,
and wearing another project's product token would route Bier's bugs to
PostgREST's issue tracker. The same reasoning applies to the OpenAPI document's
info.version. What a client can actually act on - which dialect it is
speaking - is still advertised, through externalDocs, which points at the
PostgREST release this build reproduces. The exemption is declared in the
conformance harness rather than by editing the case, so spec/ keeps recording
what PostgREST really does. See
#122.

Unbalanced parentheses in select. Upstream parses the select parameter
with pFieldForest and no eof terminator (QueryParams.hs), so Parsec keeps
the longest valid prefix and silently discards whatever input follows it -
select=name,...processes(process:name,...process_costs(cost))) is accepted
with the stray ) simply never read, and conformance case 11125 pins that
tolerance. Bier answers 400 PGRST100 instead. Matching upstream would mean
accepting arbitrary trailing garbage after a well-formed tree, so a select
truncated by a stray token would come back silently short with a 200. The
same select without the stray ) already returns exactly the body the case
expects, so only the handling of malformed input differs. See
#138.

Vary: Origin on CORS responses. Bier.Plugs.Cors echoes the request's
Origin into Access-Control-Allow-Origin rather than sending *, and a
response whose headers depend on a request header must name that header in
Vary (RFC 9111 §4.1). PostgREST builds its CORS policy with
corsVaryOrigin = False (Cors.hs), so it names nothing: a shared cache may
serve a response stamped Access-Control-Allow-Origin: https://a.example to a
request from https://b.example. Bier emits the union -
Vary: Accept, Prefer, Range, Origin - appended inside the Bier.Plugs.Vary
funnel so the v16 default is not suppressed. A wildcard
Access-Control-Allow-Origin: * is not an echo and stays bare, and CORS
preflight responses are consciously left alone: upstream answers them in the
wai-cors middleware, before the funnel that appends Vary runs at all, so
changing them would be inventing behavior rather than correcting it. See
#98.

CSV quoting. Bier's CSV writer is RFC 4180. Upstream builds CSV bodies from
PostgreSQL's record_out text with the parentheses stripped (asCsvF), which
backslash-escapes and leaves embedded newlines unquoted - a value containing
either yields malformed CSV. Bier renders the cells in SQL (so column order
and numeric text are PostgreSQL's) but keeps its own quoting. See
#110.

Benchmarks

bench/http/ contains a k6 harness that benchmarks Bier against PostgREST
head-to-head: both servers run natively against the same local PostgreSQL under
matched configuration (pool size, schema, anon role, no JWT, no compression,
HTTP/1.1 keep-alive) across four scenarios - single-row read by primary key,
filtered 25-row page, insert, and update by primary key.

The published numbers were measured against PostgREST v16.1 (2026-08),
with the full per-request auth context (role switch + request.* GUCs)
applied on both sides, and with server-side prepared statements enabled on
both sides (PostgREST's db-prepared-statements default; Bier's statement
cache).

On our reference machine (Apple M1 Max, PostgreSQL 17), Bier sustains
2.7-4.2x PostgREST's max throughput depending on the scenario, holds
median latency at parity (within ±7% of PostgREST in every scenario, ahead
on the reads), wins p90 on the read scenarios (and ties on the writes), and
holds a much tighter tail: Bier wins p99 in all four scenarios, staying
under 15 ms in every round, while PostgREST's read-side p99 reached tens of
milliseconds.

Latency is measured open-loop (k6 constant-arrival-rate, immune to
coordinated omission) at a shared arrival rate both servers sustain with zero
dropped iterations, so the comparison is apples-to-apples. These numbers are
a snapshot of one machine, not a universal claim - see
bench/http/REPORT.md
for the full tables and environment, and bench/http/run.sh to reproduce
them.

Development

Development happens in a git checkout - the conformance suite and its fixtures
are not part of the published package.

mix deps.get
mix compile
mix test            # boots a local Postgres fixture DB, then runs the suite
mix format
mix gen.parsers     # regenerate the parser modules after editing a *.ex.exs template

Run every CI gate before pushing with:

mix precommit

which chains, in order: mix deps.unlock --check-unused,
mix format --check-formatted, mix hex.audit,
mix compile --warnings-as-errors, mix credo --strict,
mix docs --warnings-as-errors, and mix test. (CI runs the same steps
individually so each gate reports separately.)

The test suite runs the spec/ submodule's numbered fixture chain
(spec/fixtures/01_roles.sql through 07_analyze.sql) into a local
bier_test database; docs/CONFORMANCE_IMPL.md covers
the database wiring, and CONTRIBUTING.md is the full
contributor guide.

Why "Bier"?

A friend asked what this side project was. I told him it's "like an urn" 🏺 -
a bier is the stand a coffin rests on. He was not amused. The name stuck. The
real motivation is more cheerful: Elixir is my favorite language, I keep falling
deeper into PostgreSQL, and serving a REST API straight from database
introspection is a great excuse to explore both - plus Bandit, Plug,
runtime module generation, and a parser built with NimbleParsec.

Happy hacking!

FAQ

Common questions

Discussion

Questions & comments · 0

Sign In Sign in to leave a comment.