Two Centuries of Elixir

Published
Jun 01, 2026
Reading time
1 min

I've been learning Elixir and have already completed my original objectives of being paid to write Elixir professionally. Still, I don't feel I'm remotely close to the level of competency I want to be at. I want to be far, far more adroit with it than I am. 

Complexity enters a software project at a few points of ingress. One is the complexity inherent in the problem. Creating an application that intelligently routes network traffic is inherently complex. The other major point of ingress is the complexity introduced by the framework or underlying technology. I want to understand Elixir well enough that the framework complexity is driven to zero.

To do this, I'm embarking on a project that I call "Two Centuries of Elixir". It is 200 projects of varying sizes that target the areas of Elixir I want to reinforce my knowledge of or learn more about. Each project is stand-alone and varies in difficulty from pretty easy to quite hard. Since almost all of the solutions I deliver are web applications, about half of this list focuses on Phoenix.

The Projects

Here are the projects - all 200 of them. They are roughly arranged as a progression, but I'm not putting any constraints on the order in which I finish them. I can pick whatever projects pique my interest.

Name Description Effort
PART I CORE ELIXIR & THE BEAM
Phase 1 Functional Foundations
1. Roll your own Enum Reimplement map, reduce, and filter from scratch with tail recursion, then benchmark them against the built-in Enum. low
2. Lazy infinite sequences Build an endless primes or Fibonacci generator with Stream.unfold/2 and pull finite slices with Enum.take/2. low
3. with pipelines Chain several functions that each return {:ok, _}/{:error, _} using with, so the flow short-circuits on the first error. low
4. Comprehension depth Use a for comprehension with multiple generators, guard filters, and :into to build a map or MapSet in one pass. low
5. Lazy file pipeline Stream a multi-GB file through File.stream! and Stream transforms so it's processed line-by-line and never fully loaded. low
6. Protocols Define a Describable protocol, implement it for a few different structs, and add an Any fallback for everything else. med
7. Custom Enumerable Implement the Enumerable protocol for a ring-buffer struct so Enum and Stream functions work on it. med
8. Custom Collectable Implement the Collectable protocol for a struct so Enum.into/2 can build it from any enumerable. med
9. Custom Access Implement the Access behaviour for a struct so get_in, put_in, and update_in can traverse it. med
10. Behaviours Define a behaviour with @callbacks and write two swappable adapter modules that implement it (e.g., two storage backends). low
11. Persistent structure Implement an immutable zipper or finger tree that supports efficient local updates without copying the entire structure. high
Phase 2 OTP & Process Design
12. GenServer fundamentals Build a bank-account GenServer with synchronous call and async cast, holding balance in state and handling code_change/3. low
13. Agent vs GenServer Implement the same counter once with Agent and once with GenServer, then compare ergonomics and when each fits. low
14. Supervisor strategy lab Start several children under a Supervisor and crash them deliberately to observe :one_for_one, :rest_for_one, and :one_for_all. low
15. Babysitter monitor Write a process that Process.monitors a worker and logs or reacts to the :DOWN message and its exit reason. med
16. :gen_statem Model a turnstile or traffic light as an explicit :gen_statem with named states and event-driven transitions. med
17. Process-per-entity Spawn one process per chat room/session on demand with DynamicSupervisor and look it up by key via Registry. med
18. Worker factory Use a DynamicSupervisor to start and stop worker processes on demand, with graceful shutdown via terminate/2. med
19. NimblePool resource pool Pool a scarce resource (e.g., a port or connection) with NimblePool, checking it out and back in per request. med
20. Supervised async tasks Run many jobs concurrently with Task.Supervisor.async_stream_nolink/3 capped by :max_concurrency, handling timeouts. med
21. Job queue from scratch Build a GenServer dispatcher that hands queued jobs to a fixed pool of worker processes, with no external library. med
22. Circuit breaker Wrap a flaky downstream call in a GenServer that fails fast after N errors and half-opens to retry later. med
23. Debounce GenServer Build a GenServer that absorbs a burst of events and emits one coalesced result after a quiet period, using Process.send_after. med
24. GenStage backpressure Build a GenStage producer/consumer pair where the consumer pulls on demand so the producer never overruns it. med
25. Flow word-count Count word frequencies across a large file in parallel with Flow, partitioning the work across all CPU cores. med
Phase 3 The BEAM Up Close
26. ETS table types Create :set, :bag, and :ordered_set ETS tables, compare lookups, and write a :ets.fun2ms match spec to filter rows. med
27. ETS leaderboard Build a top-scores board in an :ordered_set ETS table that many processes read concurrently while one process serializes writes. med
28. :counters / :atomics Share a single integer counter across many processes with :counters and confirm it stays correct without locks. med
29. :persistent_term vs ETS Store read-mostly config in both :persistent_term and ETS and benchmark read latency to see the trade-off. med
30. Memoizer Cache results of an expensive recursive function (e.g., naive Fibonacci) in ETS or a GenServer so repeats are instant. med
31. TTL + LRU cache Build an in-memory cache GenServer that expires entries after a TTL and evicts least-recently-used when full; measure hit rate. med
32. Binary internals Create refc vs heap binaries, trigger the sub-binary "leak" by slicing a large binary, and watch memory with :erlang.memory. high
33. Mailbox footgun Build a process whose selective receive slows as its mailbox grows, then fix it; measure both with :erlang.statistics. high
34. Live tracing Attach to a running function's calls and arguments in a live system using :dbg or :recon_trace, without restarting it. med
35. Runtime introspection Inspect a live process's mailbox length, reduction count, and state using Process.info/1 and :observer.start. low
36. Schedulers Spawn CPU-bound work, watch BEAM scheduler utilization, and move a blocking NIF onto a dirty scheduler to free schedulers. high
37. Hot code upgrade Change a running GenServer's state shape and migrate the old state to the new format inside code_change/3 with no downtime. high
Phase 4 Testing & Quality
38. ExUnit depth Write tests using setup, setup_all, on_exit, and tags, and learn which shared state breaks async: true. low
39. Doctests Write @doc examples that run as tests via doctest, keeping the documentation and the suite in sync. low
40. Deterministic race test Reliably reproduce a GenServer race in a test by controlling timing with a fake clock or a blocking message. high
41. Property testing Use StreamData to generate random inputs and assert a serializer's encode>decode round-trips, letting it shrink failures. med
42. Mox Define a behaviour for an external service and use Mox to set per-test expectations and verify the calls were made. med
43. Benchee Compare two implementations of a function with Benchee, reporting iterations/sec, memory, and reductions. low
44. Dialyzer Add @specs to a module, introduce a type mismatch, and run Dialyzer to catch it via success typing. med
Phase 5 Data, Parsing & Serialization
45. Binary header parsing Parse the fixed header of a PNG, WAV, or ELF file purely with binary pattern matching on its byte fields. med
46. Frame protocol codec Encode and decode messages in a length-prefixed binary framing format (4-byte length + payload) over a buffer. med
47. Compression Implement run-length encoding and/or a Huffman coder-decoder that compresses and losslessly restores a binary. high
48. Hand-rolled JSON Write a JSON tokenizer and recursive-descent parser by hand (no library) that turns a JSON string into Elixir terms. med
49. NimbleParsec grammar Define a NimbleParsec grammar for arithmetic expressions that respects operator precedence and parentheses. med
50. Safe config DSL Parse a small custom config-file format into Elixir data structures without using Code.eval_string. med
51. Streaming CSV Read a huge CSV with NimbleCSV, transform rows lazily, and write the result back out without loading the whole file. low
52. Mini changeset Build a small struct validator/caster (cast fields, check required, return errors) that mimics Ecto.Changeset without Ecto. med
53. Structural diff Walk two nested maps/lists and produce a readable diff of what was added, removed, or changed. med
Phase 6 Networking & Protocols
54. TCP echo Write a :gen_tcp server that echoes back whatever a client sends, trying both active: true and passive recv modes. med
55. TCP chat Build a TCP chat server that accepts many clients (one acceptor process each) and broadcasts each line to all the others. med
56. UDP heartbeat Receive periodic heartbeat/stat datagrams over :gen_udp and track which senders are still alive. low
57. Toy HTTP server Parse an HTTP/1.1 request line and headers off a raw :gen_tcp socket and return a valid response, no web framework. high
58. Req client Build a typed HTTP client with Req that adds retries with backoff, a custom request step, and streams large responses. med
59. Pooled HTTP Make pooled HTTP requests with Finch or Mint and benchmark throughput against a naive one-connection-per-request client. med
60. WebSocket client Connect to a public WebSocket feed with Mint.WebSocket, subscribe to a channel, and process incoming frames. high
61. SSE consumer Consume a Server-Sent-Events endpoint and expose the event stream as a lazy Elixir Stream. med
Phase 7 Distribution & Clustering
62. Manual clustering Start two named nodes, connect them with Node.connect/1, and run a function on the remote node via Node.spawn or :rpc. med
63. :pg pub/sub Build a cross-node publish/subscribe system using Erlang's :pg process groups, with no external library. med
64. :global singleton Register a single GenServer cluster-wide with :global so any node reaches the same process by name. med
65. libcluster Use libcluster's gossip strategy so nodes discover each other and form a cluster automatically on startup. med
66. Node lifecycle monitoring Subscribe with :net_kernel.monitor_nodes and rebalance or fail over work when nodes join or leave the cluster. med
67. :erpc multicall fan-out Run a query on every node at once with :erpc.multicall/4, gather the results, and compare it to legacy :rpc. med
68. Distributed lock Serialize a critical section across the whole cluster using :global.trans or :global.set_lock. med
69. libcluster on Kubernetes Configure libcluster's Kubernetes strategy so pods form a cluster via headless-service DNS discovery. med
70. Distributed ID generator Generate collision-free Snowflake-style IDs across nodes by encoding a per-node id, timestamp, and sequence. med
71. Nebulex distributed cache Set up a partitioned or replicated cache with Nebulex and broadcast invalidations so all nodes stay consistent. med
72. Leader election Elect a single leader among clustered nodes (bully algorithm or a :global lock) and re-elect when the leader dies. high
73. Work-stealing queue Build a distributed task queue where idle nodes pull/steal work from busier nodes to balance load. high
74. DeltaCrdt convergent state Replicate a counter or set across nodes with DeltaCrdt so concurrent updates merge and converge without conflicts. high
75. Horde distributed processes Run a process under Horde.DynamicSupervisor + Horde.Registry so it survives a node dying and is handed off elsewhere. high
76. Mnesia KV Build a replicated key-value store with :mnesia ram_copies on two nodes, reading and writing inside transactions. high
77. Partition behavior Simulate a network split between nodes, then heal it, and observe how :global resolves the name conflicts. high
78. FLAME elastic compute Use FLAME to run a heavy function on a freshly spun-up ephemeral node and tear it down when the work finishes. high
Phase 8 Metaprogramming
79. AST spelunking Quote expressions like if, the capture operator, and pipelines, then Macro.to_string the AST to see how they desugar. med
80. Module introspection At runtime, list a module's functions, attributes, and behaviours using __info__/1 and the Module/Code APIs. med
81. assert_match macro Write a macro that pattern-matches an expression and, on failure, prints both the expected pattern and the actual value. med
82. Custom sigils Define sigil_v and sigil_m so ~v"1.2.0" parses a version and ~m"4.99" parses money into a struct. med
83. __using__ mixin Build a module that, when used, injects helper functions and registers a module attribute in the caller. med
84. Compile-time embedding Read a data file at compile time with @external_resource and embed its contents so there's no runtime file read. med
85. Codegen from data Generate a set of functions at compile time by looping over a data list inside a macro (e.g., one function per status). med
86. Checked enum macro Write a macro that, from a list of atoms, generates an enum module with values/0, guards, and @specs. high
87. A tiny DSL Build a small block-style DSL (e.g., a validation or routing block) with defmacro that compiles to ordinary function calls. high
Phase 9 Numerical & ML Foundations
88. Nx basics Create Nx tensors, use broadcasting and defn, and compare a vectorized operation against the equivalent list comprehension. med
89. Gradient descent Implement linear regression from scratch in Nx.Defn, computing gradients and updating weights over epochs. med
90. Fractal renderer Compute a Mandelbrot or Julia set as an Nx tensor and write the escape-iteration values out as a PNG image. med
91. Explorer DataFrames Load a CSV into an Explorer DataFrame, group and aggregate columns, and compare the code to a hand-rolled Enum version. low
92. Scholar Run k-means clustering or k-NN classification on a small toy dataset using Scholar. med
93. Bumblebee embeddings Load a sentence-embedding model with Bumblebee and serve it locally through Nx.Serving to embed text. med
94. Batched inference Feed many inputs through an Nx.Serving so they're automatically batched for higher throughput. med
95. EXLA benchmark Run the same matrix multiply on the pure-Elixir BinaryBackend and the EXLA (XLA) backend and compare timings. med
96. XOR net Define, train, and evaluate a tiny feed-forward network that learns XOR using Axon. med
Phase 10 Tooling, NIFs & Releases
97. escript CLI Build a standalone command-line tool as an escript, parsing subcommands and flags with OptionParser and printing --help. low
98. Mix task Write a custom Mix.Task (e.g., mix my.gen) that inspects the project or generates a file. low
99. Owl TUI Build an interactive terminal UI with Owl featuring a live progress bar, a formatted table, and a prompt. med
100. File watcher Use the file_system library to watch a directory and re-run a command whenever a file changes. med
101. Ports Launch an external program via a Port, stream its stdout into Elixir, and shut it down cleanly. med
102. Rustler NIF Implement a CPU-bound function as a Rust NIF with Rustler and benchmark it against the pure-Elixir version. high
103. Zigler NIF Implement the same CPU-bound function as a Zig NIF with Zigler and compare ergonomics and speed to Rustler. high
104. Standalone telemetry Emit :telemetry events from a plain library function and attach a handler that aggregates and logs them. med
105. Profiling Profile a slow function with :eprof or :fprof, find the hot path, and optimize it. med
106. Release + runtime.exs Build a production release with mix release and configure it at boot from environment variables via runtime.exs. med
107. Burrito binary Package an Elixir CLI into a single self-contained native executable with Burrito. med
108. Custom build step Add a custom compiler or pre-compile step to a project through Mix aliases and the :compilers config. high
Phase 11 Pure-Elixir Synthesis
109. Actor Game of Life Implement Conway's Game of Life with one process per cell messaging its neighbors, and watch patterns emerge from local rules. high
110. Lisp/Forth interpreter Build a tokenizer, parser, and evaluator for a tiny Lisp or Forth that can run simple programs. high
111. Backtesting pipeline Stream historical OHLC bars through a GenStage pipeline that computes indicators (e.g., SMA, RSI) as they pass. high
112. Set-theoretic types Write functions and read the warnings/inferences from Elixir's new set-theoretic type system to learn how it reasons. med
PART II PHOENIX & THE WEB LAYER
Phase 12 LiveView Core Mechanics med
113. Component diff study Build the same widget once as a function component and once as a LiveComponent, and compare the diffs sent over the wire. low
114. JS commands Build accordions and dropdowns using only Phoenix.LiveView.JS commands so they toggle with no server round-trip. med
115. assign_async dashboard Load three independent dashboard widgets concurrently with assign_async, each showing its own loading/error/loaded state. med
116. start_async export Kick off a long-running export with start_async and add a Cancel button that calls cancel_async and resets the UI. med
117. stream_async list Load a large collection into a LiveView with stream_async so it streams in without ever sitting in assigns. med
118. Capped live log Show a live-tailing log that appends new lines via a LiveView stream and keeps only the most recent N with :limit. med
119. Form auto-recover Build a form that restores its in-progress values after a socket disconnect/reconnect using phx-auto-recover. med
120. Nested LiveViews Embed a child LiveView with live_render and sticky: true so it keeps its state across parent navigation. med
121. Direct-to-S3 upload Upload a file straight to S3 from the browser using LiveView external uploads with presigned URLs and a progress bar. high
122. Multi-file drop upload Accept multiple drag-and-dropped files in a LiveView upload and show client-side image previews before submitting. med
Phase 13 LiveView UI & JS Interop
123. Charting hook Render a Chart.js chart in a JS hook and update it live by pushing new data from the server with push_event. med
124. Scroll-aware header Use a JS hook that throttles scroll events and tells the server when to restyle a sticky header (e.g., shrink on scroll). med
125. Toasts Build auto-dismissing, stackable flash/toast notifications animated with Phoenix.LiveView.JS transitions. low
126. Modal stack Support opening modals on top of modals, each with focus trapping, driven by JS.show/JS.hide. med
127. Lazy tabs Build a tab bar where each tab's LiveComponent only mounts and loads its data the first time it's opened. low
128. Typeahead Build an autocomplete input that does a debounced (phx-debounce) server search and supports keyboard selection of results. med
129. Inline-editable table Let users click a table cell to edit it in place, saving on blur/enter with optimistic UI and rollback on error. med
130. Multi-step wizard Build a multi-step form that validates each step and lets users move back and forward without losing entered data. med
131. Date-range picker Build a stateful LiveComponent date-range picker with month navigation and start/end selection. med
132. Color picker Build a color picker that previews live and writes the chosen value into CSS custom properties via push_event. low
133. Tree explorer Build a collapsible file/folder tree whose child nodes load lazily the first time a node is expanded. med
134. Infinite scroll Page additional items into a LiveView stream when the user scrolls to the bottom, using phx-viewport-bottom. med
135. Resizable data grid Build a data grid with server-side column sorting and a JS hook for drag-to-resize columns that persists widths. high
136. Drag-drop reorder Make a list reorderable with a Sortable.js hook, pushing the new order to the server and persisting positions. med
137. Kanban board Build a board where cards drag between columns (Sortable.js groups), backed by LiveView streams to keep diffs small. high
138. Image cropper Crop an image client-side with Cropper.js in a hook, then upload the resulting cropped blob. high
139. Markdown editor Build a Markdown editor that renders a live HTML preview on each keystroke using Earmark. low
140. Block editor Build a Notion-style block editor with TipTap/ProseMirror in a hook and sync the document to the server as structured blocks. high
141. Command palette Build a ⌘K command palette modal with fuzzy search and full keyboard navigation via phx-keydown. med
Phase 14 Real-time & Collaboration
142. Server ticker Run a GenServer that broadcasts on an interval over PubSub and have a LiveView render each tick as it arrives. low
143. Raw Channel Build a Phoenix Channel with a hand-written JavaScript client (no LiveView) to sync multiplayer cursors. med
144. Presence online list Show a live "who's online" list using Phoenix.Presence, updating as users join and leave. med
145. Chat typing indicators Build a chat room over PubSub that broadcasts ephemeral "user is typing" state separate from sent messages. med
146. Live poll Build a poll whose result bars animate live for all viewers as votes arrive over PubSub. low
147. Live cursors Broadcast each user's mouse position over PubSub so everyone sees the others' cursors moving in real time. med
148. Selection sync Sync each user's text cursor and selection range in a shared field across clients using Phoenix.Presence metas. high
149. Distributed PubSub Run two clustered nodes and confirm a Phoenix.PubSub broadcast on one node reaches a LiveView mounted on the other. high
150. GenStage in LiveView Visualize a GenStage pipeline's backpressure live, showing demand and buffer sizes update in a LiveView. high
151. Phoenix.Sync Sync a Postgres table directly into a LiveView (and a plain JS client) in real time with Phoenix.Sync/Electric. high
Phase 15 Persistence with Ecto
152. Ecto.Multi transfer Move money between two accounts in one Ecto.Multi transaction so it fully succeeds or rolls back, handling constraint errors. med
153. Composable queries Build a function that composes an Ecto query from a filter map, adding where clauses only for the params present. med
154. Custom Ecto.Type Define a custom Ecto.Type that stores money as integer cents, plus an Ecto.Enum field for status. med
155. Embedded schema Store a validated settings blob in a JSONB column using an embedded schema and cast_embed. med
156. Optimistic locking Add a lock_version field and use Ecto's optimistic locking to reject stale concurrent updates. med
157. Soft delete Implement soft deletes with a deleted_at column and a query helper that excludes deleted rows by default. low
158. Polymorphic assoc Associate one resource (e.g., comments) with several parent types the idiomatic Ecto way, using separate join tables. med
159. Window functions Write an Ecto query using a SQL window function (via fragment/over) to rank rows within groups for a leaderboard. high
160. Full-text search Add a Postgres tsvector column with a GIN index and run ranked full-text search queries through Ecto. med
161. Materialized view Create and refresh a Postgres materialized view in a migration and query it through a read-only Ecto schema. med
162. Streaming export Export a huge result set with Repo.stream/2 inside a transaction, writing rows out without loading them all into memory. med
Phase 16 Background Work & Pipelines
163. Oban basics Define an Oban worker that retries with backoff on failure and uses a uniqueness key to avoid duplicate jobs. low
164. Oban cron Schedule a recurring digest job with Oban's Cron plugin to run on a fixed daily schedule. low
165. Oban fan-out + join Enqueue a large batch of Oban jobs and run a single callback once the entire batch has finished. high
166. Rate-limited queue Configure an Oban queue that respects a global rate limit when calling a throttled external API. med
167. Oban workflow Chain dependent Oban jobs so a job only runs after its prerequisites complete. med
168. Broadway pipeline Build a Broadway pipeline that consumes from a producer (e.g., SQS/in-memory) with batching and bounded concurrency. med
Phase 17 Auth, Scopes & Security
169. Multi-tenant scopes Use Phoenix 1.8 scopes to enforce per-tenant data access and thread the scope through a context and its LiveViews. med
170. Role-based authz Enforce role-based access with a plug for controllers and an on_mount hook for LiveViews. med
171. OAuth social login Add "Sign in with Google/GitHub" to a phx.gen.auth app using Assent (or Ueberauth), handling the provider callback and linking the external identity to a user account. med
172. API tokens Issue and verify API bearer tokens with Phoenix.Token behind an authentication plug pipeline. med
173. Login rate limiting Throttle repeated login attempts on an endpoint using Hammer to slow brute-force attacks. low
174. Cookie tampering Show how Phoenix's signed/encrypted session cookies detect tampering by editing a cookie and observing rejection. med
175. Passkey login Implement WebAuthn passkey registration and login using a wax-based library. high
Phase 18 APIs & Integration
176. Webhook receiver Build an endpoint that verifies an incoming webhook's HMAC signature (Stripe-style) and rejects replays before processing. med
177. JSON:API endpoint Expose a resource as a JSON:API endpoint with proper content negotiation and structured error envelopes. med
178. GraphQL + Dataloader Build an Absinthe GraphQL schema that batches association loads with Dataloader to avoid N+1 queries. med
179. GraphQL subscriptions Push live updates to GraphQL clients over a socket using Absinthe subscriptions. high
Phase 19 Ash Framework
180. Calculations + aggregates Define an Ash resource with calculations and aggregates and surface those computed fields in a LiveView table. med
181. Nested AshPhoenix.Form Build an AshPhoenix.Form for a parent and its has_many children with add/remove-row controls. med
182. Field-level policies Add Ash policies that restrict access to specific fields and write tests proving the denials work. high
183. AshOban trigger Use AshOban to fire a background action automatically when a record changes state. med
184. AshAuthentication Stand up AshAuthentication with password and magic-link strategies from scratch. med
Phase 20 AI / LLM In the App
185. ReqLLM streaming chat Build a chat LiveView that streams an LLM's tokens into the UI as they arrive using ReqLLM. med
186. Tool calling Let an LLM call an Elixir function via ReqLLM tool calling and render the function's result back in the chat. high
187. Instructor extraction Use Instructor to extract structured, schema-validated data (an Ecto struct) from freeform text. med
188. Semantic search Store embeddings in pgvector and rank results by cosine distance for a semantic search box over a small corpus. med
189. RAG over notes Build retrieval-augmented Q&A: embed notes into pgvector, retrieve the closest chunks, and feed them to the LLM. high
190. Zero-shot image tags Classify uploaded images with a zero-shot Bumblebee model behind a LiveView upload. med
191. Whisper transcription Transcribe a recorded audio clip to text with Whisper via Bumblebee from a LiveView mic upload. high
192. Visible agent steps Build a simple agent loop that streams its intermediate reasoning/actions into the UI with stream_async. high
193. Tidewave MCP Connect the Tidewave MCP server to an AI editor so it can read your running app's logs, schemas, and state. med
Phase 21 Testing the Web Layer
194. LiveViewTest depth Write LiveViewTest assertions covering streamed inserts/updates, form validation errors, and push_event payloads. med
195. PhoenixTest flow Write one user-journey test with PhoenixTest that runs unchanged across both dead and live views. med
196. Browser test Run that same flow in a real headless browser with phoenix_test_playwright. med
Phase 22 Observability & Deployment
197. Structured logging Emit JSON-formatted logs enriched with request metadata via a custom Logger formatter/backend. low
198. Telemetry → LiveDashboard Emit custom :telemetry events from a context and chart them on a LiveDashboard page. med
199. Custom LiveDashboard page Add a custom LiveDashboard page that displays a GenServer's live internal state. med
200. Minimal release Build a mix release served by Bandit, run it in a slim Docker image, and add a /healthz endpoint. med

Stay in the loop.
Low noise, high signal.

Each month, or so, I send out an email with a quick update on active projects and links to anything new I post. Low noise. High signal. Never spam. Hope to stay in touch.

Unsubscribe anytime. No middlemen.