<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel J. Summers</title>
    <description>The latest articles on DEV Community by Daniel J. Summers (@danieljsummers).</description>
    <link>https://dev.to/danieljsummers</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F11220%2F880348.png</url>
      <title>DEV Community: Daniel J. Summers</title>
      <link>https://dev.to/danieljsummers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danieljsummers"/>
    <language>en</language>
    <item>
      <title>A Tour of myPrayerJournal v3: Conclusion</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Tue, 07 Dec 2021 16:26:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-conclusion-3dk7</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-conclusion-3dk7</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTE: This is the final post in a series; see &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/introduction.html"&gt;the introduction&lt;/a&gt; for information on requirements and links to other posts in the series.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We’ve gone in depth on several different aspects of this application and the technologies it uses. Now, let’s zoom out and look at some big-picture lessons learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Liked
&lt;/h2&gt;

&lt;p&gt;Generally speaking, I liked everything. That does not make for a very informative post, though, so here are a few things that worked really well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplification Via htmx
&lt;/h3&gt;

&lt;p&gt;One of the key concepts in a Representational State Transfer (REST) API is that of Hypermedia as the Engine of Application State (HATEOAS). In short, this means that the state of an application is held within the hypermedia that is exchanged between client and server; and, in practice, the server is responsible for altering that state. This is completely different from the JSON API / JavaScript framework model, even if they use &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, and &lt;code&gt;PATCH&lt;/code&gt; properly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(This is a near over-simplification; the paper that initially proposed these concepts – in much, much more detail – earned its author a doctoral degree.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The simplicity of this model is great; and, when I say “simplicity,” I am speaking of a lack of complexity, not a naïveté of approach. I was able to remove a large amount of complexity and synchronization from the client/server interactions between myPrayerJournal v2 and v3. State management used to be the most complex part of the application. Now, the most complex part is the HTML rendering; since that is what controls the state, though, this makes sense. I have 25 years of experience writing HTML, and even at its most complex, it simply is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  LiteDB
&lt;/h3&gt;

&lt;p&gt;This was a very simple application - and, despite its being open for any user with a Google or Microsoft account, I have been the only regular user of the application. LiteDB’s setup was easy, implementation was easy, and it performs really well. I suspect this would be the case with many concurrent users. If the application were to grow, and I find that my suspicion was not borne out by reality, I could create a database file per user, and back up the data directory instead of a specific file. As with &lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt;, the lack of complexity makes the application easily maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Throughout this entire series of posts, most of the content would fall under this heading. There are a few things that did not fit into those posts, though.&lt;/p&gt;

&lt;h3&gt;
  
  
  htmx Support in .NET
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/bit-badger/Giraffe.Htmx"&gt;I developed &lt;code&gt;Giraffe.Htmx&lt;/code&gt; as a part of this effort&lt;/a&gt;, and mentioned that I became aware of htmx on an episode of &lt;em&gt;.NET Rocks!&lt;/em&gt;. The project I developed is very F#-centric, and uses features of the language that are not exposed in C# or VB.NET. However, there are two packages that work with the standard ASP.NET Core paradigm. &lt;a href="https://www.nuget.org/packages/Htmx/"&gt;&lt;code&gt;Htmx&lt;/code&gt;&lt;/a&gt; provides server-side support for the htmx request and response headers, similar to &lt;code&gt;Giraffe.Htmx&lt;/code&gt;, and &lt;a href="https://www.nuget.org/packages/Htmx.TagHelpers/"&gt;&lt;code&gt;Htmx.TagHelpers&lt;/code&gt;&lt;/a&gt; contains tag helpers for use in Razor, similar to what &lt;code&gt;Giraffe.ViewEngine.Htmx&lt;/code&gt; does for Giraffe View Engine. Both are written by &lt;a href="https://khalidabuhakmeh.com/"&gt;Khalid Abuhakmeh&lt;/a&gt;, a developer advocate at &lt;a href="https://www.jetbrains.com/"&gt;JetBrains&lt;/a&gt; (which generously &lt;a href="https://www.jetbrains.com/community/opensource/#support"&gt;licensed their tools to this project&lt;/a&gt;, and produces &lt;a href="https://www.jetbrains.com/lp/mono/"&gt;the best developer font ever&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;While I did not use these projects, I did look at the source, and they look good. Open source libraries go from good to great by people using them, then providing constructive feedback (and pull requests, if you are able).&lt;/p&gt;

&lt;h3&gt;
  
  
  Write about Your Code
&lt;/h3&gt;

&lt;p&gt;Yes, &lt;a href="https://blog.bitbadger.solutions/2018/a-tour-of-myprayerjournal/conclusion.html#Write-about-Your-Code"&gt;I’m cheating a bit with this one, as it was one of the takeaways from the v1 tour&lt;/a&gt;, but it’s still true. Writing about your code has several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You understand your code more fully.&lt;/li&gt;
&lt;li&gt;Others can see not just the code you wrote, but understand the thought process behind it.&lt;/li&gt;
&lt;li&gt;Readers can provide you feedback. &lt;em&gt;(This may not always seem helpful; regardless of its tone, though, thinking through whether the point of their critique is justified can help you learn.)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, really, knowledge sharing is what makes the open-source ecosystem work. Closed / proprietary projects have their place, but if you do something interesting, write about it!&lt;/p&gt;

&lt;h2&gt;
  
  
  What Could Be Better
&lt;/h2&gt;

&lt;p&gt;Dove-tailing from the previous section, writing can also help you think through your code; if you try to explain it, and and have trouble, that should serve as a warning that there are improvements to be had. These are the areas where this project has room to get better.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deferred Features
&lt;/h3&gt;

&lt;p&gt;There were 2 changes I had originally planned for myPrayerJournal v3 that did not get accomplished. One is a new “pray through the requests” view, with a distraction-free next-card-up presentation. The other is that updating requests sends them to the bottom of the list, even if they have not been marked as prayed; this will require calculating a separate “last prayed” date instead of using the “as of” date from the latest history entry.&lt;/p&gt;

&lt;p&gt;The migration introduced a third deferred change. When v1/v2 ran in the browser, the dates and times were displayed in the user’s local timezone. With the HTML being generated on the server, though, dates and times are now displayed in UTC. The purpose of the application is to focus the user’s attention on their prayer requests, not to make them have to do timezone math in their head! htmx has an &lt;code&gt;hx-headers&lt;/code&gt; attribute that specifies headers to pass along with the request; I plan to use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions"&gt;a JavaScript call&lt;/a&gt; to set a header on the &lt;code&gt;body&lt;/code&gt; tag when a full page loads (&lt;code&gt;hx-headers&lt;/code&gt; is inherited), then use that timezone to adjust it back to the user’s current timezone.&lt;/p&gt;

&lt;h3&gt;
  
  
  That LiteDB Mapping
&lt;/h3&gt;

&lt;p&gt;I did a good bit of tap-dancing in the &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-data-store.html"&gt;LiteDB data model and mapping descriptions&lt;/a&gt;, mildly defending the design decisions I had made there. The recurrence should be designed differently, and there should be individual type mappings rather than mapping the entire document. Yes, it worked for my purpose, and this project was more about Vue to htmx than ensuring a complete F#-to-LiteDB mapping of domain types. As I implement the features above, though, I believe I will end up fixing those issues as well.&lt;/p&gt;




&lt;p&gt;Thank you for joining me on this tour; I hope it has been enjoyable, and maybe even educational.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>htmx</category>
      <category>giraffe</category>
      <category>litedb</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal v3: The Data Store</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Wed, 01 Dec 2021 17:33:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-the-data-store-3bc6</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-the-data-store-3bc6</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTE: This is the fourth post in a series; see &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/introduction.html"&gt;the introduction&lt;/a&gt; for information on requirements and links to other posts in the series.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;myPrayerJournal v1 used &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; with &lt;a href="https://docs.microsoft.com/en-us/ef/core/"&gt;Entity Framework Core&lt;/a&gt; for its backing store (which had &lt;a href="https://blog.bitbadger.solutions/2018/a-tour-of-myprayerjournal/the-data-store.html"&gt;a stop on the v1 tour&lt;/a&gt;). v2 used &lt;a href="https://ravendb.net/"&gt;RavenDB&lt;/a&gt;, and while I didn’t write a tour of it, you can &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/2.2/src/MyPrayerJournal.Api/Data.fs"&gt;see the data access logic&lt;/a&gt; if you’d like. Let’s take a look at the technology we used for v3.&lt;/p&gt;

&lt;h2&gt;
  
  
  About LiteDB
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.litedb.org/"&gt;LiteDB&lt;/a&gt; is a single-file, in-process database, similar to &lt;a href="https://sqlite.org/"&gt;SQLite&lt;/a&gt;. It uses a document model for its data store, storing Plain Old CLR Objects (POCOs) as Binary JSON (BSON) documents in its file. It supports cross-collection references, customizable mappings, different access modes, and transactions. It allows documents to be queried via LINQ syntax or via its own SQL-like language.&lt;/p&gt;

&lt;p&gt;As I mentioned in the introduction, I picked it up for another project, and really enjoyed the experience. Its configuration could not be easier – the connection string is &lt;em&gt;literally&lt;/em&gt; a path and file name – and it had good performance as well. The way it locks its database file, I can copy it while the application is up, which is great for backups. It was definitely a good choice for this project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Domain Model
&lt;/h2&gt;

&lt;p&gt;When I converted from PostgreSQL to RavenDB, the data structure ended up with one document per request; the history log and notes were stored as F# lists (arrays in JSON) within that single document. RavenDB supports indexes which can hold calculated values, so I had made an index that had the latest request text, and the latest time an action was taken on a request. When v2 displayed any list of requests, I queried the index, and got the calculated fields for free.&lt;/p&gt;

&lt;p&gt;The model for v3 is very similar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Request is the identifying record for a prayer request&lt;/span&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CLIMutable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;NoComparison&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;NoEquality&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;/// The ID of the request&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RequestId&lt;/span&gt;
  &lt;span class="c1"&gt;/// The time this request was initially entered&lt;/span&gt;
  &lt;span class="n"&gt;enteredOn&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;
  &lt;span class="c1"&gt;/// The ID of the user to whom this request belongs ("sub" from the JWT)&lt;/span&gt;
  &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt;
  &lt;span class="c1"&gt;/// The time at which this request should reappear in the user's journal by manual user choice&lt;/span&gt;
  &lt;span class="n"&gt;snoozedUntil&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;
  &lt;span class="c1"&gt;/// The time at which this request should reappear in the user's journal by recurrence&lt;/span&gt;
  &lt;span class="n"&gt;showAfter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;
  &lt;span class="c1"&gt;/// The type of recurrence for this request&lt;/span&gt;
  &lt;span class="n"&gt;recurType&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Recurrence&lt;/span&gt;
  &lt;span class="c1"&gt;/// How many of the recurrence intervals should occur between appearances in the journal&lt;/span&gt;
  &lt;span class="n"&gt;recurCount&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;int16&lt;/span&gt;
  &lt;span class="c1"&gt;/// The history entries for this request&lt;/span&gt;
  &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;History&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;
  &lt;span class="c1"&gt;/// The notes for this request&lt;/span&gt;
  &lt;span class="n"&gt;notes&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Note&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes would probably be good here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;CLIMutable&lt;/code&gt; attribute allows this non-nullable record type to be null, and generates a zero-argument constructor that reflection-based processes can use to create an instance. Both of these are needed to interface with a C#-oriented data layer.&lt;/li&gt;
&lt;li&gt;By default, F# creates comparison and equality implementations for record types. This type, though, is a simple data transfer object, so the &lt;code&gt;NoEquality&lt;/code&gt; and &lt;code&gt;NoComparison&lt;/code&gt; attributes prevent these from being generated.&lt;/li&gt;
&lt;li&gt;Though not shown here, &lt;code&gt;History&lt;/code&gt; has an “as-of” date/time, an action that was taken, and an optional request text field; &lt;code&gt;Note&lt;/code&gt; has the same thing, minus the action but requiring the text field.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Customizing the POCO Mapping
&lt;/h2&gt;

&lt;p&gt;If you look at the fields in the &lt;code&gt;Request&lt;/code&gt; type above, you’ll spot exactly one primitive data type (&lt;code&gt;int16&lt;/code&gt;). &lt;code&gt;Instant&lt;/code&gt; comes from &lt;a href="https://nodatime.org/"&gt;NodaTime&lt;/a&gt;, but the remainder are custom types. These are POCOs, but not your typical POCOs; by tweaking the mappings, we can get a much more efficient BSON representation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discriminated Unions
&lt;/h3&gt;

&lt;p&gt;F# supports discriminated unions (DUs), which can be used in different ways to construct a domain model in such a way that an invalid state cannot be represented (TL;DR - “make invalid states unrepresentable”). One way of doing this is via the single-case DU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// The identifier of a user (the "sub" part of the JWT)&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requests are associated with the user, via the &lt;code&gt;sub&lt;/code&gt; field in the JWT received from &lt;a href="https://auth0.com/"&gt;Auth0&lt;/a&gt;. That field is a string; but, in the handler that retrieves this from the &lt;code&gt;Authorization&lt;/code&gt; header, it is returned as &lt;code&gt;UserId [sub-value]&lt;/code&gt;. In this way, that string cannot be confused with any other string (such as a note, or a prayer request).&lt;/p&gt;

&lt;p&gt;Another way DUs can be used is to generate enum-like types, where each item is its own type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// How frequently a request should reappear after it is marked "Prayed"&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Recurrence&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Immediate&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Hours&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Days&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Weeks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, these four values will refer to a recurrence, and it will take no others. This barely scratches the surface on DUs, but it should give you enough familiarity with them so that the rest of this makes sense.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For the F#-fluent - you may be asking “Why didn’t he define this with &lt;code&gt;Hours of int16&lt;/code&gt;, &lt;code&gt;Days of int16&lt;/code&gt;, etc. instead of putting the number in &lt;code&gt;Request&lt;/code&gt; separate from the type?” The answer is a combination of evolution – this is the way it worked in v1 – and convenience. I very well could have done it that way, and probably should at some point.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Converting These Types in myPrayerJournal v2
&lt;/h3&gt;

&lt;p&gt;F# does an excellent job of transparently representing DUs, &lt;code&gt;Option&lt;/code&gt; types, and others to F# code, while their underlying implementation is a CLR type; however, when they are serialized using traditional reflection-based serializers, the normally-transparent properties appear in the output. RavenDB (and Giraffe, when v1 was developed) uses &lt;a href="https://www.newtonsoft.com/json"&gt;JSON.NET&lt;/a&gt; for its serialization, so it was easy to write a converter for the &lt;code&gt;UserId&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// JSON converter for user IDs&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;UserIdJsonConverter&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="o"&gt;__.&lt;/span&gt;&lt;span class="nc"&gt;WriteJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="o"&gt;__.&lt;/span&gt;&lt;span class="nc"&gt;ReadJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonReader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this converter, a property “x”, with a user ID value of “abc”, would be serialized as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Case"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UserId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this converter, though, the same structure would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a database where you are querying on a value, or a JSON-consuming front end web framework, the latter is definitely what you want.&lt;/p&gt;

&lt;h3&gt;
  
  
  Converting These Types in myPrayerJournal v3
&lt;/h3&gt;

&lt;p&gt;With all of the above being said – LiteDB does not use JSON.NET; it uses its own custom &lt;code&gt;BsonMapper&lt;/code&gt; class. This means that the conversions for these types would need to change. LiteDB does support creating mappings for custom types, though, so this task looked to be a simple conversion task. As I got into it, though, I realized that nearly every field I was using needed some type of conversion. So, rather than create converters for each different type, I created one for the document as a whole.&lt;/p&gt;

&lt;p&gt;It was surprisingly straightforward, once I figured out the types! Here are the functions to convert the request type to its BSON equivalent, and back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Map a request to its BSON representation&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;requestToBson&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BsonValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BsonDocument&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nn"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"enteredOn"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enteredOn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToUnixTimeMilliseconds&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;       &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nn"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"snoozedUntil"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snoozedUntil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToUnixTimeMilliseconds&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"showAfter"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;showAfter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToUnixTimeMilliseconds&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"recurType"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;    &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nn"&gt;Recurrence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recurType&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"recurCount"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nc"&gt;BsonValue&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recurCount&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"history"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nc"&gt;BsonArray&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;historyToBson&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nc"&gt;BsonArray&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notes&lt;/span&gt;   &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;noteToBson&lt;/span&gt;    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofList&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;upcast&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;

&lt;span class="c1"&gt;/// Map a BSON document to a request&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;requestFromBson&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BsonValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RequestId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofString&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"_id"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsString&lt;/span&gt;
    &lt;span class="n"&gt;enteredOn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FromUnixTimeMilliseconds&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"enteredOn"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsInt64&lt;/span&gt;
    &lt;span class="n"&gt;userId&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UserId&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"userId"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsString&lt;/span&gt;
    &lt;span class="n"&gt;snoozedUntil&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FromUnixTimeMilliseconds&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"snoozedUntil"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsInt64&lt;/span&gt;
    &lt;span class="n"&gt;showAfter&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FromUnixTimeMilliseconds&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"showAfter"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsInt64&lt;/span&gt;
    &lt;span class="n"&gt;recurType&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Recurrence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofString&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"recurType"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsString&lt;/span&gt;
    &lt;span class="n"&gt;recurCount&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;int16&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"recurCount"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsInt32&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"history"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsArray&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;historyFromBson&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofSeq&lt;/span&gt;
    &lt;span class="n"&gt;notes&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"notes"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="nc"&gt;AsArray&lt;/span&gt;   &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;noteFromBson&lt;/span&gt;    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofSeq&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these round-trips as the same value; line 6 (&lt;code&gt;doc["userId"]&lt;/code&gt;) stores the string representation of the user ID, while line 19 (&lt;code&gt;userId =&lt;/code&gt;) creates a strongly-typed &lt;code&gt;UserId&lt;/code&gt; from the string stored in database.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The downside to this technique is that LINQ won’t work; passing a &lt;code&gt;UserId&lt;/code&gt; would look for the default serialized version, not the simplified string version. This is not a show-stopper, though, especially for such a small application as this. If I had wanted to use LINQ for queries, I would have written several type-specific converters instead.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Querying the Data
&lt;/h2&gt;

&lt;p&gt;In v2, there were two different types; &lt;code&gt;Request&lt;/code&gt; was what was stored in the database, and &lt;code&gt;JournalRequest&lt;/code&gt; was the type that included the calculated fields included in the index. This conversion came into the application; &lt;code&gt;ofRequestFull&lt;/code&gt; is a function that performs the calculations, and returns an item which has full history and notes, while &lt;code&gt;ofRequestLite&lt;/code&gt; does the same thing without the history and notes lists.&lt;/p&gt;

&lt;p&gt;With that knowledge, here is the function that retrieves the user’s current journal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Retrieve the user's current journal&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;journalByUserId&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LiteDatabase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backgroundTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;jrnl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Find&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EQ&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;toListAsync&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;jrnl&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="nn"&gt;JournalRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofRequestLite&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastStatus&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Answered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sortBy&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asOf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofSeq&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 3 contains the LiteDB query; when it is done, &lt;code&gt;jrnl&lt;/code&gt; has the type &lt;code&gt;System.Collections.Generic.List&amp;lt;Request&amp;gt;&lt;/code&gt;. This “list” is different than an F# list; it is a concrete, doubly-linked list. F# lists are immutable, recursive item/tail pairs, so F# views the former as a form of sequence (as it extends &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;). Thus, the &lt;code&gt;Seq&lt;/code&gt; module calls in the return statement are the appropriate ones to use. They execute lazily, so filters should appear as early as possible; this reduces the number of latter transformations that may need to occur.&lt;/p&gt;

&lt;p&gt;Looking at this example, if we were to sort first, the entire sequence would need to be sorted. Then, when we filter out the requests that are answered, we would remove items from that sequence. With sorting last, we only have to address the full sequence once, and we are sorting a (theoretically) smaller number of items. Conversely, we do have to run the &lt;code&gt;map&lt;/code&gt; on the original sequence, as &lt;code&gt;lastStatus&lt;/code&gt; is one of the calculated fields in the object created by &lt;code&gt;ofRequestLite&lt;/code&gt;. Sometimes you can filter early, sometimes you cannot.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Is this micro-optimizing? Maybe; but, in my experience, taking a few minutes to think through collection pipeline ordering is a lot easier than trying to figure out why (or where) one starts to bog down. Following good design principles isn’t premature optimization, IMO.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting a Database Connection
&lt;/h2&gt;

&lt;p&gt;The example in the previous section has a final parameter of &lt;code&gt;(db: LiteDatabase)&lt;/code&gt;. As Giraffe sits atop ASP.NET Core, myPrayerJournal uses the traditional dependency injection (DI) container. Here is how it is configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Configure dependency injection&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bldr&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebApplicationBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LiteDatabase&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bldr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetConnectionString&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nn"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ensureDb&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;
  &lt;span class="n"&gt;bldr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Services&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LiteDatabase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;
  &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connection string comes from &lt;code&gt;appsettings.json&lt;/code&gt;. &lt;code&gt;Data.Startup.ensureDb&lt;/code&gt; makes sure that requests are indexed by user ID, as that is the parameter by which request lists are queried; this also registers the converter functions discussed above. LiteDB has an option to open the file for shared access or exclusive access; this implementation opens it for exclusive access, so we can register that connection as a singleton. (LiteDB handles concurrent queries itself.)&lt;/p&gt;

&lt;p&gt;Getting the database instance out of DI is, again, a standard Giraffe technique:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Get the LiteDB database&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LiteDatabase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be called in any request handler; here is the handler that displays the journal cards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GET /components/journal-items&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;journalItems&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;requiresAuthentication&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notAuthorized&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;backgroundTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;jrnl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journalByUserId&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shown&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jrnl&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snoozedUntil&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;showAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;renderComponent&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nn"&gt;Views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Journal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journalItems&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;shown&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making LiteDB Async
&lt;/h2&gt;

&lt;p&gt;I found it curious that LiteDB’s data access methods do not have async equivalents (ones that would return &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; instead of just &lt;code&gt;T&lt;/code&gt;). My supposition is that this is a case of YAGNI. LiteDB maintains a log file, and makes writes to that first; then, when it’s not busy, it synchronizes the log to the file it uses for its database. However, I wanted to control when that occurs, and the rest of the request/function pipelines are async, so I set about making async wrappers for the applicable function calls.&lt;/p&gt;

&lt;p&gt;Here are the data retrieval functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Convert a sequence to a list asynchronously (used for LiteDB IO)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;toListAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToList&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;/// Convert a sequence to a list asynchronously (used for LiteDB IO)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;firstAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FirstOrDefault&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FromResult&lt;/span&gt;

&lt;span class="c1"&gt;/// Async wrapper around a request update&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;doUpdate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;LiteDatabase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Update&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;
  &lt;span class="nn"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CompletedTask&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, for the log synchronization, an extension method on &lt;code&gt;LiteDatabase&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Extensions on the LiteDatabase class&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;LiteDatabase&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="c1"&gt;/// Async version of the checkpoint command (flushes log)&lt;/span&gt;
  &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saveChanges&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Checkpoint&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="nn"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CompletedTask&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these actually make the underlying library use async I/O; however, they do let the application’s main thread yield until the I/O is done. Also, despite the &lt;code&gt;saveChanges&lt;/code&gt; name, this is &lt;strong&gt;not required&lt;/strong&gt; to save data into LiteDB; it is there once the insert or update is done (or, optionally, when the transaction is committed).&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;As I draft this, this paragraph is on line 280 of this post’s source; the entire &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Data.fs"&gt;Data.fs&lt;/a&gt; file is 209 lines, including blank lines and comments. The above is a moderately long-winded explanation of what is nicely terse code. If I had used traditional C#-style POCOs, the code would likely have been shorter still. The backup of the LiteDB file is right at half the size of the equivalent RavenDB backup, so the POCO-to-BSON mapping paid off there. I’m quite pleased with the outcome of using LiteDB for this project.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/conclusion.html"&gt;final stop on the tour&lt;/a&gt; will wrap up with overall lessons learned on the project.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>litedb</category>
      <category>data</category>
      <category>giraffe</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal v3: Bootstrap Integration</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Mon, 29 Nov 2021 16:51:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-bootstrap-integration-39k7</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-bootstrap-integration-39k7</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTE: This is the third post in a series; see &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/introduction.html"&gt;the introduction&lt;/a&gt; for information on requirements and links to other posts in the series.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Many modern Single Page Application (SPA) frameworks include (or have plugins for) CSS transitions and effects. Combined with the speed of not having to do a full refresh, this is one of their best features. One might not think that a framework like &lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt;, which simply swaps out sections of the page, would have this; but if one were to think that, one would be wrong. Sadly, though, I did not utilize those aspects of htmx while I was migrating myPrayerJournal from v2 to v3; however, I will highlight the htmx way to do this in last section of this post.&lt;/p&gt;

&lt;p&gt;myPrayerJournal v2 used a &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt; plugin that provided Bootstrap v4 support; myPrayerJournal v3 uses &lt;a href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt; v5. The main motivation I had to remain with Bootstrap was that I liked the actual appearance, and I know how it works. The majority of my “learning” on this project dealt with htmx; I did not want to add a UI redesign to the mix. Before we jump into the implementation, let me briefly explain the framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Bootstrap
&lt;/h2&gt;

&lt;p&gt;Bootstrap was originally called Twitter Bootstrap; it was the CSS framework that Twitter developed in their early iterations. It was, by far, the most popular framework at the time, and it was innovative in its grid layout system. Long before there was browser support for the styles that make layouts much easier to develop, and more responsive to differing screen sizes, Bootstrap’s grid layout and size breakpoints made it easy to build a website that worked for desktop, tablet, or phone. Of course, there is a limit to what you can do with styling, so Bootstrap also has a JavaScript library that augments these styles, enabling the interactivity to which the modern web user is accustomed.&lt;/p&gt;

&lt;p&gt;Version 5 of Bootstrap continues this tradition; however, it brings in even more utility classes, and supports Flex layouts as well. It is a mature library that continues to be maintained, and the project’s philosophy seems to be “just enough” - it’s not going to do everything for everyone, but in the majority of cases, it has exactly what the developer needs. It is not a bloated library that needs tree-shaking to avoid a ridiculous download size.&lt;/p&gt;

&lt;p&gt;It is, by far, the largest payload in the initial page request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrap - 48.6 kB (CSS is 24.8 kB; JavaScript is 23.8 kB, deferred until after render)&lt;/li&gt;
&lt;li&gt;htmx - 11.8 kB&lt;/li&gt;
&lt;li&gt;myPrayerJournal - 4.4 kB (CSS is 1.2 kB, JavaScript is 3.2 kB)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, this gets the entire style and script, and allows us to use their layouts and interactive components. But, how do we get that interactivity from the server?&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooking in to the htmx Request Pipeline
&lt;/h2&gt;

&lt;p&gt;htmx provides &lt;a href="https://htmx.org/reference/#events"&gt;several events&lt;/a&gt; to which an application can listen. In myPrayerJournal v3, &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/wwwroot/script/mpj.js#L72"&gt;I used &lt;code&gt;htmx:afterOnLoad&lt;/code&gt;&lt;/a&gt; because I did not need the new content to be swapped in yet when the function fired. There are &lt;code&gt;afterSwap&lt;/code&gt; and &lt;code&gt;afterSettle&lt;/code&gt; events which will fire once those events have occurred, if you need to defer processing until those are complete.&lt;/p&gt;

&lt;p&gt;There are two different Bootstrap script-driven components myPrayerJournal uses; let’s take a look at toasts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Toast &lt;del&gt;to&lt;/del&gt; Via htmx
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://getbootstrap.com/docs/5.1/components/toasts/"&gt;Toasts&lt;/a&gt; are pop-up notifications that appear on the screen, usually for a short time, then fade out. In some cases, particularly if the toast is alerting the user to an error, it will stay on the screen until the user dismisses it, usually by clicking an “x” in the upper right-hand corner &lt;em&gt;(even if the developer used a Mac!)&lt;/em&gt;. Bootstrap provides a host of options for their toast component; for our uses, though, we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Place toasts in the bottom right-hand corner;&lt;/li&gt;
&lt;li&gt;Allow multiple toasts to be visible at once;&lt;/li&gt;
&lt;li&gt;Auto-hide success toasts; require others to be dismissed manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are several different aspects that make this work.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Toaster
&lt;/h3&gt;

&lt;p&gt;Just like IRL toast comes out of a toaster, our toasts need a place from which to emerge. In the &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-user-interface.html#%E2%80%9CNew-Page%E2%80%9D-in-htmx"&gt;prior post&lt;/a&gt;, I mentioned that the footer does not get reloaded when a “page” request is made. There is also an element above the footer that also remains across these requests - defined here as the “toaster” (my term, not Bootstrap’s).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Element used to display toasts&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;toaster&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;ariaLive&lt;/span&gt; &lt;span class="s2"&gt;"polite"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;ariaAtomic&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"toastHost"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"toast-container position-absolute p-3 bottom-0 end-0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"toasts"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This renders two empty &lt;code&gt;div&lt;/code&gt;s with the appropriate style attributes; toasts placed in the &lt;code&gt;#toasts&lt;/code&gt; &lt;code&gt;div&lt;/code&gt; will display as we want them to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Showing the Toast
&lt;/h3&gt;

&lt;p&gt;Bootstrap provides &lt;code&gt;data-&lt;/code&gt; attributes that can make toasts appear; however, since we are creating these in script, we need to use their JavaScript functions. The message coming from the server has the format &lt;code&gt;TYPE|||The message&lt;/code&gt;. Let’s look at &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/wwwroot/script/mpj.js#L9"&gt;the showToast function&lt;/a&gt; (the largest custom JavaScript function in the entire application):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mpj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nx"&gt;showToast&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;|||&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typ&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;span class="me-auto"&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;typ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/span&amp;gt;`&lt;/span&gt;

      &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toast-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn-close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data-bs-dismiss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toast-body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toastEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`toast bg-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;danger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; text-white`&lt;/span&gt;
    &lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-live&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assertlive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aria-atomic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden.bs.toast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toasts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Toast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toastEl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;autohide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s what’s going on in the code above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line 4 splits the level from the message&lt;/li&gt;
&lt;li&gt;Lines 6-20 (&lt;code&gt;let header&lt;/code&gt;) create a header and close button if the message is not a success&lt;/li&gt;
&lt;li&gt;Lines 22-24 (&lt;code&gt;const body&lt;/code&gt;) create the body &lt;code&gt;div&lt;/code&gt; with attributes Bootstrap’s styling expects&lt;/li&gt;
&lt;li&gt;Lines 26-30 (&lt;code&gt;const toastEl&lt;/code&gt;) create the &lt;code&gt;div&lt;/code&gt; that will contain the toast&lt;/li&gt;
&lt;li&gt;Line 31 adds an event handler to remove the element from the DOM once the toast is hidden&lt;/li&gt;
&lt;li&gt;Lines 32 and 34 add the optional header and mandatory body to the toast &lt;code&gt;div&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Line 35 adds the toast to the page (within the &lt;code&gt;toasts&lt;/code&gt; inner &lt;code&gt;div&lt;/code&gt; defined above)&lt;/li&gt;
&lt;li&gt;Line 36 initializes the Bootstrap JavaScript component, auto-hiding on success, and shows the toast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;(If you’ve never used JavaScript to create elements that are added to an HTML document, this probably looks weird and verbose; if you have, you look at it and think “well, they’re not wrong…”)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, we have our toaster, we know how to put &lt;del&gt;bread&lt;/del&gt; notifications in it - but how do we get the notifications from the server?&lt;/p&gt;

&lt;h3&gt;
  
  
  Receiving the Toast
&lt;/h3&gt;

&lt;p&gt;The code to handle this is part of the &lt;code&gt;htmx:afterOnLoad&lt;/code&gt; handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;htmx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htmx:afterOnLoad&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hdrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAllResponseHeaders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;// Show a message if there was one in the response&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hdrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-toast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;mpj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getResponseHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-toast&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks for a custom HTTP header of &lt;code&gt;X-Toast&lt;/code&gt; (all headers are lowercase from that &lt;code&gt;xhr&lt;/code&gt; call), and if it’s found, we pass the value of that header to the function above. This check occurs after every htmx network request, so there is nothing special to configure; “page” requests are not the only requests capable of returning a toast notification.&lt;/p&gt;

&lt;p&gt;There is one more part; how does the toast get to the browser?&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending the Toast
&lt;/h3&gt;

&lt;p&gt;The last paragraph gave it away; we set a header on the response. This seems straightforward, and is in most cases; but &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-user-interface.html#POST-Redirect-GET"&gt;once again, POST-Redirect-GET&lt;/a&gt; (P-R-G) complicates things. Here are the final two lines of the successful path of &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Handlers.fs#L503"&gt;the request update handler&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="nn"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pushSuccess&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="s2"&gt;"Prayer request updated successfully"&lt;/span&gt; &lt;span class="n"&gt;nextUrl&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;seeOther&lt;/span&gt; &lt;span class="n"&gt;nextUrl&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we set a message in the response header, then redirect (remember that &lt;code&gt;XMLHttpRequest&lt;/code&gt; handles redirects silently), the header gets lost in the redirect. Here, &lt;code&gt;Messages.pushSuccess&lt;/code&gt; places the success message (and return URL) in a dictionary, indexed by the user’s ID. Within the function that renders every result (partial, “page”-like, or full results), this dictionary is checked for a message and URL, and if one exists, it includes it. (If it is returned to the function below, it has already been removed from the dictionary.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Send a partial result if this is not a full page load (does not append no-cache headers)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;partialStatic&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pageTitle&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;backgroundTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;isPartial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsHtmx&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IsHtmxRefresh&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;pageCtx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pageContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;pageTitle&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;isPartial&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;pageCtx&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; 
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;||&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
          &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
              &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;Messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
              &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setHttpHeader&lt;/span&gt; &lt;span class="s2"&gt;"X-Toast"&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;withHxPush&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;writeView&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
              &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;writeView&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
          &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;writeView&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick overview of this function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line 4 determines if this an htmx boosted request (a “page”-like requests)&lt;/li&gt;
&lt;li&gt;Line 5 creates a rendering context for the page&lt;/li&gt;
&lt;li&gt;Line 6 renders the view to a string, calling &lt;code&gt;partial&lt;/code&gt; or &lt;code&gt;view&lt;/code&gt; with the page rendering context&lt;/li&gt;
&lt;li&gt;Lines 10-13 are only executed if a user is logged on, and line 12 is the one that appends a message and a new URL&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;A quick note about line 12:&lt;/em&gt;&lt;/strong&gt; the &lt;code&gt;&amp;gt;=&amp;gt;&lt;/code&gt; operator joins Giraffe &lt;code&gt;HttpHandler&lt;/code&gt;s together. An &lt;code&gt;HttpHandler&lt;/code&gt; takes an &lt;code&gt;HttpContext&lt;/code&gt; and the next function to be executed, and returns a &lt;code&gt;Task&amp;lt;HttpContext option&amp;gt;&lt;/code&gt; (an asynchronous call that may or may not return a context). If there is no context returned, the chain stops; the function can also return an altered context. It is good practice for an &lt;code&gt;HttpHandler&lt;/code&gt; to make a single change to the context; this keeps them simple, and allows them to be plugged in however the developer desires. Thus, the &lt;code&gt;setHttpHeader&lt;/code&gt; call adds the &lt;code&gt;X-Toast&lt;/code&gt; header, the &lt;code&gt;withHxPush&lt;/code&gt; call adds the &lt;code&gt;HX-Push&lt;/code&gt; header, and the &lt;code&gt;writeView&lt;/code&gt; call sets the response body to the rendered view.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The new URL part does not actually make the browser do anything; it simply pushes the given URL onto the browser’s history stack. Technically, the browser receives the content from the P-R-G as the response to its POST; as we’re replacing the current page, though, we need to make sure the URL stays in sync.&lt;/p&gt;

&lt;p&gt;Of note is that not all toasts are this complex. For example, the “cancel snooze” handler return looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;withSuccessMessage&lt;/span&gt; &lt;span class="s2"&gt;"Request unsnoozed"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestItem&lt;/span&gt; &lt;span class="n"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…while the &lt;code&gt;withSuccessMessage&lt;/code&gt; handler is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Add a success message header to the response&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;withSuccessMessage&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;HttpHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;sprintf&lt;/span&gt; &lt;span class="s2"&gt;"success|||%s"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setHttpHeader&lt;/span&gt; &lt;span class="s2"&gt;"X-Toast"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dictionary, no redirect, just a single response that will show a toast.&lt;/p&gt;

&lt;p&gt;You made it - the toast section is toast! There is one more interesting interaction, though; that of the modal dialog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modal Dialogs
&lt;/h2&gt;

&lt;p&gt;Bootstrap’s &lt;a href="https://getbootstrap.com/docs/5.1/components/modal/"&gt;implementation of modal dialogs&lt;/a&gt; also uses JavaScript; however, for the purposes of the modals used in myPrayerJournal v3, we can use the &lt;code&gt;data-&lt;/code&gt; attributes to show them. Here is the view for a modal dialog that allows the user to snooze a request (hiding it from the active list until the specified date); this is rendered a single time on the journal view page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;             &lt;span class="s2"&gt;"snoozeModal"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;          &lt;span class="s2"&gt;"modal fade"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;tabindex&lt;/span&gt;       &lt;span class="s2"&gt;"-1"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;ariaLabelledBy&lt;/span&gt; &lt;span class="s2"&gt;"snoozeModalLabel"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;ariaHidden&lt;/span&gt;     &lt;span class="s2"&gt;"true"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"modal-dialog modal-sm"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"modal-content"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"modal-header"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;h5&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"modal-title"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"snoozeModalLabel"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="s2"&gt;"Snooze Prayer Request"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"btn-close"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"bs-dismiss"&lt;/span&gt; &lt;span class="s2"&gt;"modal"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;ariaLabel&lt;/span&gt; &lt;span class="s2"&gt;"Close"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"modal-body"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"snoozeBody"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
      &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"modal-footer"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"snoozeDismiss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"btn btn-secondary"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"bs-dismiss"&lt;/span&gt; &lt;span class="s2"&gt;"modal"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="s2"&gt;"Close"&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;#snoozeBody&lt;/code&gt; is empty; we fill that when the user clicks the snooze icon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;     &lt;span class="s2"&gt;"button"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;    &lt;span class="s2"&gt;"btn btn-secondary"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;    &lt;span class="s2"&gt;"Snooze Request"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;     &lt;span class="s2"&gt;"bs-toggle"&lt;/span&gt; &lt;span class="s2"&gt;"modal"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;     &lt;span class="s2"&gt;"bs-target"&lt;/span&gt; &lt;span class="s2"&gt;"#snoozeModal"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;hxGet&lt;/span&gt;    &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"/components/request/{reqId}/snooze"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;hxTarget&lt;/span&gt; &lt;span class="s2"&gt;"#snoozeBody"&lt;/span&gt;
  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;hxSwap&lt;/span&gt;   &lt;span class="nn"&gt;HxSwap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InnerHtml&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;icon&lt;/span&gt; &lt;span class="s2"&gt;"schedule"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses &lt;code&gt;data-bs-toggle&lt;/code&gt; and &lt;code&gt;data-bs-target&lt;/code&gt;, Bootstrap attributes, to show the modal. It also uses &lt;code&gt;hx-get&lt;/code&gt; to load the snooze form for that particular request, with &lt;code&gt;hx-target&lt;/code&gt; targeting the &lt;code&gt;#snoozeBody&lt;/code&gt; &lt;code&gt;div&lt;/code&gt; from the modal definition. Here is how that form is defined:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// The snooze edit form&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;snooze&lt;/span&gt; &lt;span class="n"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToString&lt;/span&gt; &lt;span class="s2"&gt;"yyyy-MM-dd"&lt;/span&gt;
  &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;hxPatch&lt;/span&gt;  &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"/request/{RequestId.toString requestId}/snooze"&lt;/span&gt;
    &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;hxTarget&lt;/span&gt; &lt;span class="s2"&gt;"#journalItems"&lt;/span&gt;
    &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;hxSwap&lt;/span&gt;   &lt;span class="nn"&gt;HxSwap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OuterHtml&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"form-floating pb-3"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"until"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="s2"&gt;"until"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"form-control"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;min&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s2"&gt;"until"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="s2"&gt;"Until"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"text-end mb-0"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"btn btn-primary"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="s2"&gt;"Snooze"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the form uses &lt;code&gt;hx-patch&lt;/code&gt; to submit the data to the snooze endpoint. The target for the response, though, is &lt;code&gt;#journalItems&lt;/code&gt;; this is the element that holds all of the prayer request cards. Snoozing a request will remove it from the active list, so the list needs to be refreshed; this will make that happen.&lt;/p&gt;

&lt;p&gt;Look back at the modal definition; at the bottom, there is a “Close” button. We will use this to dismiss the modal once the update succeeds. In the Giraffe handler to snooze a request, here is its &lt;code&gt;return&lt;/code&gt; statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;withSuccessMessage&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Request snoozed until {until.until}"&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hideModal&lt;/span&gt; &lt;span class="s2"&gt;"snooze"&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Components&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;journalItems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;hideModal&lt;/code&gt; handler?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// Hide a modal window when the response is sent&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hideModal&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;setHttpHeader&lt;/span&gt; &lt;span class="s2"&gt;"X-Hide-Modal"&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, it’s another HTTP header! One can certainly get carried away with custom HTTP headers, but their very existence is to communicate with the client (browser) outside of the visible content of the page. Here, we’re passing the name “snooze” to this header; in our &lt;code&gt;htmx:afterOnLoad&lt;/code&gt; handler, we’ll consume this header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;htmx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htmx:afterOnLoad&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hdrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAllResponseHeaders&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="c1"&gt;// Hide a modal window if requested&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hdrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-hide-modal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getResponseHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-hide-modal&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dismiss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The “Close” button on our modal was given the &lt;code&gt;id&lt;/code&gt; of &lt;code&gt;snoozeDismiss&lt;/code&gt;; this mimics the user clicking the button, which Bootstrap’s &lt;code&gt;data-&lt;/code&gt; attributes handle from there. Of all the design choices and implementations I did in this conversion, this part strikes me as the most “hack”y. However, I did try to hook into the Bootstrap modal itself, and hide it via script; however, it didn’t like initializing a modal a second time, and I could not get a reference to it from the &lt;code&gt;htmx:afterOnLoad&lt;/code&gt; handler. Clicking the button works, though, even when it’s done from script.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Transitions in htmx
&lt;/h2&gt;

&lt;p&gt;This post has already gotten much longer than I had planned, but I wanted to make sure I covered this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When htmx requests are in flight, the framework makes it easy to &lt;a href="https://htmx.org/docs/#indicators"&gt;show indicators&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I mentioned swapping and settling when discussing the events htmx exposes. The way this is done, &lt;a href="https://htmx.org/docs/#css_transitions"&gt;CSS transitions&lt;/a&gt; will render as expected. They have &lt;a href="https://htmx.org/examples/animations/"&gt;a host of examples&lt;/a&gt; to spark your imagination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As I was keeping the UI the same, I did not end up using these options; however, their presence demonstrates that htmx is a true batteries-included SPA framework.&lt;/p&gt;




&lt;p&gt;Up next, we’ll step away from the front end and &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-data-store.html"&gt;dig into LiteDB&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>giraffe</category>
      <category>htmx</category>
      <category>bootstrap</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal v3: The User Interface</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Sun, 28 Nov 2021 14:17:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-the-user-interface-2k2k</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-the-user-interface-2k2k</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTE: This is the second post in a series; see &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/introduction.html"&gt;the introduction&lt;/a&gt; for information on requirements and links to other posts in the series.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you are a seasoned Single Page Application (SPA) framework developer, you likely think about interactivity in a particular way. Initially, I focused on replacing each interactive piece in isolation. In the end, though, requests for “pages” returned almost everything but the HTML head info and the displayed footer - and I was happy about it. Keep that in mind as I walk you down the path I have already traveled; keep an open mind, and read to the end before forming strong opinions either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $.05 Tour of Pug and Giraffe View Engine
&lt;/h2&gt;

&lt;p&gt;Understanding the syntax of both &lt;a href="https://pugjs.org/"&gt;Pug&lt;/a&gt; and &lt;a href="https://giraffe.wiki/view-engine"&gt;Giraffe View Engine&lt;/a&gt; will help you if you click any of the source code example links. While a complete explanation of these two templating languages would make this long post much longer than it already is, here are some short examples of their syntax. Using a string variable &lt;code&gt;who&lt;/code&gt; with the contents “World”, we will show both languages rendering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"example"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"greeting"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Hello &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;World&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;myPrayerJournal v2 used Pug templates in &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt; to render the user interface. Pug uses indentation-sensitive tag/content pairs (or blocks), with JavaScript syntax for attributes, to generate HTML. To generate the example paragraph, the shortest template would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;p.greeting(id="example") Hello #[strong= who]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;myPrayerJournal v3 uses Giraffe View Engine, which uses F# lists to generate HTML from a very HTML-looking domain-specific language (DSL). The example paragraph would be generated with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="s2"&gt;"greeting"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="s2"&gt;"Hello "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;strong&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;who&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given those examples, let’s dig into the conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Menu
&lt;/h2&gt;

&lt;p&gt;The menu across the top of the application was one of the first items I needed to convert. The menu needs to be different, depending on whether there is a user logged on or not. Also, if a user is logged on, the menu can still be different; the “Snoozed” menu item only appears if the user has any snoozed requests. The application uses &lt;a href="https://auth0.com/"&gt;Auth0&lt;/a&gt; to manage users (which is how it is open to Microsoft and Google accounts), and I wanted to preserve this; my requests are tied to the ID provided by Auth0, so that did not need to change.&lt;/p&gt;

&lt;p&gt;In the Vue version, the system used Auth0’s SPA library that exposed whether there was a user logged on or not. Also, once a user was logged on, the API sent all the user’s active requests, which included snoozed requests; once this API call returned, the application can turn on the “Snoozed” menu item. In the &lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt; version, though, this information is all generated on the server. My initial process was to use an &lt;code&gt;hx-get&lt;/code&gt; to get the menu HTML snippet, using an &lt;code&gt;hx-trigger&lt;/code&gt; of &lt;code&gt;load&lt;/code&gt; to fill in this spot of the page when the page was loaded. I also (initially) implemented a custom HTML header to include in responses, and if that header was found, I would trigger a refresh on the menu; the eventual solution included the navbar in “page” refreshes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(See the &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/2.2/src/app/src/components/common/Navigation.vue"&gt;Vue “Navigation” component&lt;/a&gt; that became the &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Views/Layout.fs#L39"&gt;Giraffe View Engine “navbar” function&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  “New Page” in htmx
&lt;/h2&gt;

&lt;p&gt;This leads directly into a discussion of how myPrayerJournal is still considered a SPA. In the Vue version, “pages” were Vue Single-File Components (SFCs) under the &lt;code&gt;/components&lt;/code&gt; directory. (In the years since myPrayerJournal v1, the default Vue template has changed to place these SFCs under &lt;code&gt;/views&lt;/code&gt;, while &lt;code&gt;/components&lt;/code&gt; is reserved for shared components.) These view components rendered into a custom component within the &lt;code&gt;main&lt;/code&gt; tag (using Vue router’s &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/2.2/src/app/src/App.vue#L16"&gt;&lt;code&gt;router-view&lt;/code&gt; tag&lt;/a&gt;), while the &lt;code&gt;nav&lt;/code&gt; component was reactive, based on the user logging on/off and snoozing requests.&lt;/p&gt;

&lt;p&gt;In myPrayerJournal v3, “page” views target the &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Views/Layout.fs#L140"&gt;&lt;code&gt;#top&lt;/code&gt; &lt;code&gt;section&lt;/code&gt; element&lt;/a&gt;. If the request is for a full page load, the HTML &lt;code&gt;head&lt;/code&gt; content is rendered, as is the &lt;code&gt;body&lt;/code&gt;‘s &lt;code&gt;footer&lt;/code&gt; content; none of these change until a new version of the application is released. If the request is an htmx request, though, the only thing rendered is a new &lt;code&gt;#top&lt;/code&gt; section, which includes the navigation bar and the page content. While this does approach a full “page load”, there are some key differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page contents are refreshed based on one HTTP request (no extra request or processing required for the navbar);&lt;/li&gt;
&lt;li&gt;The HTML &lt;code&gt;head&lt;/code&gt; content is responsible for most of the large HTTP requests, such as those for JavaScript libraries (and is excluded from non-full-page views);&lt;/li&gt;
&lt;li&gt;The page footer is not included.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the difference between the &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Views/Layout.fs#L136"&gt;full &lt;code&gt;view&lt;/code&gt; layout&lt;/a&gt; and the &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Views/Layout.fs#L147"&gt;&lt;code&gt;partial&lt;/code&gt; layout&lt;/a&gt;. Also, within the application’s request handlers, there is a &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Handlers.fs#L162"&gt;partial return function&lt;/a&gt; that determines whether this is an htmx-initiated page view request (in which case a partial view is returned) or a full page request (which returns the entire template).&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the Page Title
&lt;/h2&gt;

&lt;p&gt;One of the most unexpectedly-vexing parts of a SPA is determining how the browser’s title bar will be updated when navigation occurs. &lt;em&gt;(I understand why it’s challenging; what I do not understand is why it took major frameworks so long to devise a built-in way of handling this.)&lt;/em&gt; Coming from that world, I had originally implemented yet another custom header, pushing the title from the server, and used a request listener to update the title if the header was present. As I dug in further, though, I learned that htmx will update the document title any time a request payload has an HTML &lt;code&gt;title&lt;/code&gt; element in its &lt;code&gt;head&lt;/code&gt;. If you look at both layouts in the preceding paragraph, you’ll notice that they include a &lt;code&gt;head&lt;/code&gt; element with a &lt;code&gt;title&lt;/code&gt; tag. This is how easy it should be, and with htmx, this is how easy it is.&lt;/p&gt;

&lt;p&gt;At this point, there is a pattern emerging. The thought process behind an htmx-powered website is much different than a JavaScript-based SPA framework; and, in the majority of cases, it has been less complex. Now, let me contradict what I just said.&lt;/p&gt;

&lt;h2&gt;
  
  
  POST-Redirect-GET
&lt;/h2&gt;

&lt;p&gt;In myPrayerJournal v2, updating a prayer request followed this flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Display the edit page, with the request details filled in&lt;/li&gt;
&lt;li&gt;When the user saved the request, return an empty &lt;code&gt;200 OK&lt;/code&gt; response&lt;/li&gt;
&lt;li&gt;Using Vue, display a notification, refresh the journal, then re-render the page where the user had been when they clicked “Edit” (there are multiple places from which requests can be edited)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While there are no redirects here, this is the classic traditional-web-application scenario where the “POST-Redirect-GET” (P-R-G) pattern is used. By using this pattern, the “Back” button on the browser still works. If you try to go back to the result of a POST request, the browser will warn you that your action will result in the data being resubmitted (not usually what you want to do). By redirecting, though, the result of a POST becomes a GET, which does not change any data. For traditional web applications, this is the user-friendliest way to handle updates.&lt;/p&gt;

&lt;p&gt;In the htmx examples, they show &lt;a href="https://htmx.org/examples/click-to-edit/"&gt;an example of inline editing&lt;/a&gt;. This led to my first plan - change the request edit “page” to be a component, where the HTML for the displayed list was replaced by the form, and then the “Save” action returns the new HTML. This requires no P-R-G, as these actions have no effect on the “Back” button. It worked fine, but there were some things that weren’t quite right:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New requests needed their own page; I was going to have to duplicate the edit form for the “new” page, and introduce some complexity in determining how to render the results.&lt;/li&gt;
&lt;li&gt;Some updates required refreshing the list of requests, not just replacing the text and action buttons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, I was also starting to realize “if you think something is hard to do in htmx, you probably aren’t trying to do it correctly.” So, I decided to try to replicate the “edit page” flow of v2 in v3. Creating &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3/src/MyPrayerJournal/Views/Request.fs#L139"&gt;the page&lt;/a&gt; was easy enough, and I was able to use the &lt;code&gt;returnTo&lt;/code&gt; parameter in the function to both provide a “Cancel” button and redirect the user to the right place after saving. Easy, right? Well… Not quite.&lt;/p&gt;

&lt;p&gt;htmx uses &lt;code&gt;XMLHttpRequest&lt;/code&gt; (XHR) to send its requests, which has some interesting behavior; it follows redirects! When I submitted my form, it received the request (with htmx’s &lt;code&gt;HX-Request&lt;/code&gt; header set), and the server returned the redirect. XHR saw this, and followed it; however, it used the same method. (It was POSTing to the new URL.) The fix for this, though, was not easy to find, but easy to implement; use HTTP response code &lt;code&gt;303&lt;/code&gt; (see other) instead of &lt;code&gt;307&lt;/code&gt; (moved temporarily). Using this, combined with using &lt;code&gt;hx-target="#top"&lt;/code&gt; on the form, allowed the P-R-G pattern to work successfully without double-POSTing and without a full-page refresh.&lt;/p&gt;

&lt;h2&gt;
  
  
  htmx Support in Giraffe and Giraffe View Engine
&lt;/h2&gt;

&lt;p&gt;As I developed this, I was also building up extensions for Giraffe to handle the htmx request and response headers, as well as the attributes needed to generate htmx-aware markup from Giraffe View Engine. &lt;a href="https://github.com/bit-badger/Giraffe.Htmx"&gt;This project&lt;/a&gt;, called Giraffe.Htmx, is now published on NuGet. There are two packages; &lt;code&gt;Giraffe.Htmx&lt;/code&gt; provides server-side support for the request and response headers, and &lt;code&gt;Giraffe.ViewEngine.Htmx&lt;/code&gt; provides support for generating htmx attributes. I &lt;a href="https://blog.bitbadger.solutions/2021/introducing-giraffe-htmx.html"&gt;wrote about it&lt;/a&gt; when it was released, so I won’t rehash the entire thing here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final UI Thoughts
&lt;/h2&gt;

&lt;p&gt;htmx is much less complex than any other front-end JavaScript SPA framework I have ever used - which, for context, includes &lt;a href="https://angular.io/"&gt;Angular&lt;/a&gt;, Vue, &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, &lt;a href="https://emberjs.com/"&gt;Ember&lt;/a&gt;, &lt;a href="https://aurelia.io/"&gt;Aurelia&lt;/a&gt;, and &lt;a href="https://elm-lang.org/"&gt;Elm&lt;/a&gt;. Both in development and in production use, I cannot tell that the payloads are slightly larger; navigation is fast and smooth. Though I have yet to change anything since going live with myPrayerJournal v3, I know that maintenance will be quite straightforward (to be further explored in the conclusion post).&lt;/p&gt;

&lt;p&gt;The UI for myPrayerJournal uses &lt;a href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt;, a UI framework which has its own script, and htmx plays quite nicely with it. The &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/bootstrap-integration.html"&gt;next post&lt;/a&gt; in this series will describe how I interact with both Bootstrap and htmx, using modals and toasts on this “traditional” web application.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>htmx</category>
      <category>giraffe</category>
      <category>vue</category>
    </item>
    <item>
      <title>Introducing Giraffe.Htmx</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Fri, 26 Nov 2021 21:41:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/introducing-giraffehtmx-3824</link>
      <guid>https://dev.to/danieljsummers/introducing-giraffehtmx-3824</guid>
      <description>&lt;p&gt;&lt;a href="https://giraffe.wiki/"&gt;Giraffe&lt;/a&gt; is a library that sits atop ASP.NET Core and allows developers to build web applications in a functional style; &lt;code&gt;dotnet new giraffe&lt;/code&gt; is &lt;em&gt;literally&lt;/em&gt; my starting point when I begin a new web application project. &lt;em&gt;(Rather than write three more sentences filled with effusive praise, I’ll just leave it at that; it’s great.)&lt;/em&gt; It also provides a view engine (that builds upon &lt;a href="https://suave.io/"&gt;Suave&lt;/a&gt;‘s “experimental” view engine) which uses an F# DSL to define HTML in a strongly-typed way. It has been incredibly efficient for a while, but with .NET’s work over the past two releases at improving performance, and Giraffe’s adoption of those techniques, it is lightning fast.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt; is a library that brings interactivity to HTML through the use of attributes and HTTP headers. Whereas projects like &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt;, &lt;a href="https://angular.io/"&gt;Angular&lt;/a&gt;, and &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; prescribe completely different programming paradigms than traditional web development, htmx provides partial-page-swapping and progressive enhancement within straight HTML. This brings a lot of the benefits of the SPA architecture to vanilla HTML, without requiring a completely different paradigm than the one we have used on the web for 30 years. In practice, this greatly reduces the complexity required to produce an interactive web application.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Giraffe.Htmx&lt;/strong&gt; project provides a bridge between these two libraries. The project consists of two different NuGut packages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Giraffe.Htmx&lt;/code&gt; provides extensions to Giraffe (and its exposure of ASP.NET Core’s &lt;code&gt;HttpContext&lt;/code&gt;) that expose the &lt;a href="https://htmx.org/docs/#request_headers"&gt;request headers&lt;/a&gt; which htmx uses, and provides Giraffe-style &lt;code&gt;HttpHandler&lt;/code&gt;s to set htmx’s recognized &lt;a href="https://htmx.org/docs/#response_headers"&gt;response headers&lt;/a&gt;. The request headers are exposed as &lt;code&gt;Option&lt;/code&gt;s, and if present, are converted to the expected type. Response headers can be set in a similar way (i.e., passing &lt;code&gt;true&lt;/code&gt; instead of &lt;code&gt;"true"&lt;/code&gt; for a boolean header).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let myHandler : HttpHander =
fun next ctx -&amp;gt;
match ctx.Request.HxPrompt with
 | Some prompt -&amp;gt; ... // do something with the text the user provided
 | None -&amp;gt; ... // the user provided no text (likely was not even prompted)
 ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Giraffe.ViewEngine.Htmx&lt;/code&gt; extends Giraffe’s view engine with &lt;a href="https://htmx.org/docs/#attributes"&gt;attribute&lt;/a&gt; functions (ex. &lt;code&gt;_hxBoost&lt;/code&gt; equates to &lt;code&gt;hx-boost="true"&lt;/code&gt;) to generate HTML with htmx attributes. As with the headers, the values for each attribute are expected in their strongly-typed form, and the library handles the necessary string conversion. For attributes that have a defined set of values, there are also modules that provide those values; the example below demonstrates both the attributes and the &lt;code&gt;HxTrigger&lt;/code&gt; module.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let autoLoad =
 div [\_hxGet "/this/endpoint"; \_hxTrigger HxTrigger.Load] [str "Loading..."] 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head over to &lt;a href="https://github.com/bit-badger/Giraffe.Htmx"&gt;the project site&lt;/a&gt; for NuGet links and more examples!&lt;/p&gt;

&lt;p&gt;p.s. As of this publishing (on dev.to), the current version of &lt;code&gt;Giraffe.Htmx&lt;/code&gt; is v0.9.3 (includes the &lt;code&gt;HX-Retarget&lt;/code&gt; header introduced in htmx v1.6.1), and &lt;code&gt;Giraffe.ViewEngine.Htmx&lt;/code&gt; is v0.9.2. Both are release-ready, and will go 1.0 in early 2022.&lt;/p&gt;

</description>
      <category>giraffe</category>
      <category>fsharp</category>
      <category>htmx</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal v3: Introduction</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Fri, 26 Nov 2021 16:04:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-introduction-3flp</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-v3-introduction-3flp</guid>
      <description>&lt;p&gt;This is the first of 5 posts in this series.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 0: Introduction&lt;/strong&gt; &lt;em&gt;(this post)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-user-interface.html"&gt;Part 1: The User Interface&lt;/a&gt;&lt;/strong&gt; - A look at htmx and Giraffe working together to create the web UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/bootstrap-integration.html"&gt;Part 2: Bootstrap Integration&lt;/a&gt;&lt;/strong&gt; - A little bit of JavaScript goes a long way&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-data-store.html"&gt;Part 3: The Data Store&lt;/a&gt;&lt;/strong&gt; - Migration to and usage of LiteDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/conclusion.html"&gt;Part 4: Conclusion&lt;/a&gt;&lt;/strong&gt; - Lessons learned and areas for improvement&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Around 3 years ago, I wrote an 8-part series called &lt;a href="https://blog.bitbadger.solutions/2018/a-tour-of-myprayerjournal/introduction.html"&gt;“A Tour of myPrayerJournal”&lt;/a&gt;, recounting the decisions and implementation of its initial release. Version 2 did not get its own tour, as it used a similar architecture. There were also some nagging library issues that were never resolved, leading to v2 being an overall unsatisfying step in the evolution of this application.&lt;/p&gt;

&lt;p&gt;When &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt; v3 was announced, this sounded like a great opportunity, with first-class TypeScript support and a new component syntax that promised better performance and a better developer experience. This past summer, I completed a project with the mature Vue v3 framework, and was generally pleased with the results. Just after I returned to my previously abandoned migration attempt on this project (with early Vue v3 support), I heard about &lt;strong&gt;htmx&lt;/strong&gt;. With a few attributes, and a server that can handle a few HTTP headers, you can build an interactive site, with performance rivaling or exceeding that of the typical Single Page Application (SPA) - or, at this point, so they claimed.&lt;/p&gt;

&lt;p&gt;I also picked up &lt;strong&gt;LiteDB&lt;/strong&gt; on another project over the summer, and it worked well. I thought, why not give these technologies a try, and see if I would like the result?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(SPOILER ALERT: I did!)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Requirements
&lt;/h2&gt;

&lt;p&gt;Requirements for v3 were, for the most part, to update the application to Vue v3. Without rehashing the entire list (see the other intro post), the basic idea is that a prayer request is represented by a card, and this card keeps up with all changes made to it. Also, the system can present the cards that are active, arranged with the oldest action date first, and allow you to tick through the cards. (This is the flow to enable the user to “pray through their list.”)&lt;/p&gt;

&lt;p&gt;The goal is to remain a minimalist program; the focus should be on prayer, not using a website. To that end, I had envisioned a “one-at-a-time” scenario that would clear out distractions and present the cards in the same order. I had also planned to separate the “last prayed” date from the “last activity” date; currently, updating the text of a request moves it to the bottom of the stack. However, both of these improvements were deferred to v3.1; v3 restores the (adequate) functionality of v1, while being much lighter-weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;This stack did not go through nearly as many iterations as v1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://giraffe.wiki/"&gt;Giraffe&lt;/a&gt;&lt;/strong&gt; is a library that enables F# developers to create ASP.NET Core endpoints in a functional style. It’s a mature library (v1 used Giraffe!), and continues to be improved. It also provides an optional “Giraffe View Engine,” which will get more attention in the user interface post; the views for v3 are produced via this view engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt;&lt;/strong&gt; is a JavaScript library that asks… well, several questions. Why should links and buttons be the only interactive elements? Why should you have to replace the whole page every time? What would HTML look like if it had been developed the way a typical programming language would be? It uses a small set of attributes to answer these questions differently, making interactive sites possible without writing any JavaScript. (The custom JavaScript file in v3 is 82 lines, including comments - and the majority of that is &lt;a href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt; interaction.)&lt;/p&gt;

&lt;p&gt;Since, in the htmx way, the web server returns rendered HTML, the requests can be a bit larger than the equivalent API calls that return JSON for a SPA framework to render. However, this is offset somewhat by the fact that the browser just has to swap that HTML fragment in; the processing is faster and much less complex.&lt;/p&gt;

&lt;p&gt;What really swung me over the fence to giving it a shot, though, was a point Carson (the author of the library) made while talking with Carl and Richard on the &lt;em&gt;&lt;a href="https://www.dotnetrocks.com/?show=1749"&gt;.NET Rocks!&lt;/a&gt;&lt;/em&gt; podcast. Having a server render the HTML, and the browser merely displaying it, keeps your application logic on the server; the only JavaScript you need to write is what is required for the user interface. This eliminates a host of synchronization issues with SPAs and their associated APIs - duplicating shapes of data, ensuring calculations are in sync, etc. It also keeps your application logic from needing to be exposed to the public Internet; this doesn’t entirely prevent exploits, but the prospective hacker doesn’t start with a full copy of your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://litedb.org/"&gt;LiteDB&lt;/a&gt;&lt;/strong&gt; could be described as SQLite for documents. Collections of Plain-Old CLR Objects (POCOs) can be stored, retrieved, searched, indexed, and deleted, all while running in the current process, and requiring no separate database server install. While it does not require any special configuration to do this, it does also provide the ability to transform these objects. This gives complete control as to how much or how little transformation you want to specify; and, as we’ll see in part 3, this came in handy for this application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where We Go from Here
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-user-interface.html"&gt;next post&lt;/a&gt;, we’ll take a look at Giraffe, its View Engine, htmx, and how they all work together. The &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/bootstrap-integration.html"&gt;post after that&lt;/a&gt; will dive into the aforementioned 82 lines of JavaScript to see how we can control Bootstrap’s client/browser behavior from the server. After that, we’ll &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/the-data-store.html"&gt;dig in on LiteDB&lt;/a&gt;, to include how we serialize some common F# constructs. Finally, we’ll &lt;a href="https://blog.bitbadger.solutions/2021/a-tour-of-myprayerjournal-v3/conclusion.html"&gt;wrap up the series&lt;/a&gt; with overarching lessons learned, and other thoughts which may not fit nicely into one of the other posts.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>htmx</category>
      <category>giraffe</category>
      <category>litedb</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal: Conclusion</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Sun, 02 Sep 2018 07:10:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-conclusion-296l</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-conclusion-296l</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTE: This is the final post of an 8-post series; see &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-introduction-jl5"&gt;the introduction&lt;/a&gt; for all of them, and the requirements for which this software was built.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the course of this tour, we’ve meandered through client side code, server side code, a database, and documentation. However, the experience of developing an application is more than just the sum of the assembled technologies and techniques. Let’s take a look at some of these “lessons learned” and opinions formed through this process. (This post will use the first-person singular pronouns “I” / “me” / “my” a lot more than the previous ones.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Vue Is Awesome – But…
&lt;/h2&gt;

&lt;p&gt;As I tried different SPA frameworks, they were interesting and fun, but a lot more work than I expected. Then, I came across &lt;a href="https://vuejs.org"&gt;Vue&lt;/a&gt;, and its paradigm and flow just clicked. Single file components are great; it was so nice to not have to go digging through a site-wide CSS file looking for styles that affected the elements in the component. I just had to scroll down! While I did put the common CSS in &lt;code&gt;App.vue&lt;/code&gt;, the application’s top component, anything unique that component did was right there. There are also all kinds of Vue-aware packages that you can add and use, that add their own elements/components to the process; &lt;a href="https://github.com/ElemeFE/element"&gt;Element UI&lt;/a&gt;, &lt;a href="https://bootstrap-vue.js.org"&gt;Bootstrap Vue&lt;/a&gt;, and &lt;a href="https://github.com/Justineo/vue-awesome"&gt;Vue-Awesome&lt;/a&gt; were three of the ones I used at different points in development. Since it’s a JavaScript framework, you can use vanilla JS packages as well; myPrayerJournal uses &lt;a href="https://momentjs.com"&gt;moment.js&lt;/a&gt; to display relative dates (“last activity 8 minutes ago”) and format dates for display.&lt;/p&gt;

&lt;p&gt;Then… I ran the build process, and the bundles were huge – as in, multiple megabytes huge! We changed our implementation of Vue-Awesome to only import the icons we used in the application (to be fair to them, that is the project’s recommendation). Element also seemed to be rather heavy. One of the last issues I worked before final release was removing Bootstrap Vue and just using straight HTML/CSS for layout and flow (which is another lesson we’ll explore below). There are guides on configuring &lt;a href="https://webpack.js.org"&gt;Webpack&lt;/a&gt; to help make moment’s bundle smaller (and that project has an open issue to refactor so that it’s more friendly to a “just import the part you need” paradigm).&lt;/p&gt;

&lt;p&gt;None of this is meant as a knock of any of the fine projects I’ve named up to this point. It was also near the end of the project when I converted to the Vue CLI v3 template, which uses a version of Webpack that has a much better “tree-shaking” algorithm. (This means that, if it can establish that code is never executed, it excludes it from the bundle.) myPrayerJournal v1.0’s modern “vendor” bundle (the one with the libraries) is 283K, while the legacy bundle is 307K; while that’s not lightning fast on mobile, it’s also comparable to a lot of other sites, and since page updates happen through the API, it is fast once it’s loaded.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt; : Be smart about what you import.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt; : The JavaScript ecosystem evolves quickly. This was published September 2nd, 2018, describing development that occurred September 2016 through August 2018; a good bit of this is likely already obsolete. :)&lt;/p&gt;

&lt;h2&gt;
  
  
  You Might Not Need…
&lt;/h2&gt;

&lt;p&gt;We mentioned above that the site eventually was written with simple HTML and CSS. Many of the more popular packages and utilities were created to make up for deficiencies, either in the browser ecosystem or among the differing browser vendors. With the recent efforts by browser vendors to support published standards, though, many of these packages are used for reasons that distill to comfort and inertia. As before, this is not a knock on these projects; they filled a definite need, and continue to work as the basis for a lot of deployed, executing code.&lt;/p&gt;

&lt;p&gt;For new development, though, existing standards – and their support – may be sufficient. There are some great sites that detail how certain things can be done using plain JavaScript or CSS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://youmightnotneedjquery.com"&gt;You Might Not Need jQuery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/stuyam/YouMightNotNeedBootstrap/tree/gh-pages"&gt;You Might Not Need Bootstrap&lt;/a&gt; &lt;em&gt;(for this one, you have to read the HTML yourself; looks like it’s not hosted at the given URL anymore)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youmightnotneed.com/lodash"&gt;You Might Not Need Lodash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caniuse.com"&gt;Can I Use ___?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used the last one quite a bit. I also extensively referred to &lt;a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/"&gt;CSS Tricks’ “A Complete Guide to Flexbox”&lt;/a&gt; post. When I decided to rework the layout without Bootstrap, I thought the replacement would be CSS Grid; however, Flexbox was more than enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt; : Use a framework if you want, but don’t assume it’s the only way to do things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt; : If you want to shrink your bundle size, 20-30 lines of your own code can sometimes save you 20-30K (or more).&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn Go
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Ladies and gentlemen of the Internet class of 2018 -&lt;br&gt;  Learn Go.&lt;br&gt;&lt;br&gt;
If I could offer you only one tip for the future,&lt;br&gt;  Go would be it.&lt;br&gt;&lt;br&gt;
– &lt;em&gt;&lt;a href="http://www.metrolyrics.com/everybodys-free-to-wear-sunscreen-lyrics-baz-luhrmann.html"&gt;(with apologies to Baz Luhrmann)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://golang.org"&gt;Go&lt;/a&gt; is a systems programming language. It was developed at Google, to help them better utilize their hardware. It natively supports concurrent processing (which can be done in parallel, but is distinct from “parallel programming”); has an opinionated code formatter; forces you to address calls that may error; and is terribly efficient. When myPrayerJournal was running with the Go backend, the working size in RAM was around 10MB. Let me say that again, this time with feeling - &lt;strong&gt;the working size for a database-accessing, HTTP-listening, dynamic web service was 10MB of RAM!&lt;/strong&gt; If you have ever profiled a web server process, you know that it’s nearly ludicrous how small this is. For comparison, the process working set for the F#/Giraffe/EF Core version of the backend runs between 60-80MB, and includes another ~256MB of shared working set memory.&lt;sup&gt;2&lt;/sup&gt; (An Apache2 process running PHP can run in the 256MB range as well.)&lt;/p&gt;

&lt;p&gt;Why am I recommending a technology that I ultimately moved away from before the v1.0 release? Well, other than “did you read the last paragraph?!?!”, the short answer is “it’s the future, and will change how you code in every other language.” The fact that it forces you to deal with every single thing that may error makes it robust; but, if you learn to develop with it, you will find yourself thinking about error handling more fully than you did before – and I say this as a person who already coded error handlers as I coded the happy path.&lt;/p&gt;

&lt;p&gt;Why did I move away from a technology on which I’m so bullish? Well, for starters, F# is the language that “clicks” for me in the same way that Vue did as a client-side framework; its development paradigm just makes sense with the way I think about structuring code. I completed a project that used F# and Giraffe (which I hope to take open-source soon), and that gave me the confidence to move forward with a third attempt at an F# API. &lt;em&gt;(Third time’s the charm, right?)&lt;/em&gt; Also, while Entity Framework generated some pretty verbose SQL statements, EF Core more or less generates what I would have written anyway, &lt;strong&gt;plus&lt;/strong&gt; it takes care of populating the objects it returns from the database.&lt;/p&gt;

&lt;p&gt;I also found the development process awkward, though not unwieldy. &lt;em&gt;(They’re probably not going to adopt that as their slogan…)&lt;/em&gt; Much has been written about the &lt;code&gt;GOPATH&lt;/code&gt; environment variable, but once you get into it, it starts to make sense. &lt;code&gt;go get&lt;/code&gt; is great at pulling down your dependencies, and the way it does it, you can peruse the source code to see exactly what they are doing. Also, it was not too difficult to develop on Windows, but build executables for Linux – which, in the “systems” programming work, is a really cool feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt; : Learn Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write about Your Code
&lt;/h2&gt;

&lt;p&gt;This wasn’t a lesson I learned on this project; this was one I’d known for a while. There are &lt;em&gt;(at least)&lt;/em&gt; two distinct benefits to writing about your code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can help you learn even more than you may have learned when you were writing the code itself. As developers, we sometimes forget to step back and look at the body of work we’ve created. If you write about it, you have to form a coherent view about it so you can explain it to others; this helps you long-term. Also, people who know more about the environment can chime in on what you’ve written, which also not only helps you learn, …&lt;/li&gt;
&lt;li&gt;It helps build community. If you hit a snag, and someone in the community around that technology helps you get past it, writing about your experience means that the next person to encounter that issue may not have to ask; if their searching leads them to your post, they can fix it and move on. This applies doubly if you could &lt;strong&gt;not&lt;/strong&gt; get help from the community; you might be the one who surfaces this issue/technique, and moves the entire community forward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt; : Write about your code; participate in the community around your technology to whatever extent you are able.&lt;/p&gt;

&lt;p&gt;If you’ve been with us for this entire tour – thank you. I hope you’ve learned something; I know I have, not just through the development of &lt;a href="https://prayerjournal.me"&gt;myPrayerJournal&lt;/a&gt;, but through the course of writing about it. And, certainly, if you feel that this application could help you in any way, help yourself. It is and will always be free, and &lt;a href="https://bitbadger.solutions"&gt;Bit Badger Solutions&lt;/a&gt; (and DJS Consulting before it) has, as of this writing, a 14-year streak of no known data breaches; your prayer requests are safe with us.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;There are chunk-splitting techniques that can be used to make the initial download smaller, and load other portions on-demand. Moment.js, for example, isn’t needed for the default “Welcome to myPrayerJournal” page. We could defer loading that until the user has logged in, as the journal page definitely needs it; we’d still have to download the same amount, but it would be spread out across 2 requests. Opportunities to save some size in the initial download are still out there, but 283K is just above the 244K suggested bundle size, so we went forward with it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;The server on which I host myPrayerJournal already has other .NET Core processes running on it, so the shared memory size has already been allocated.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vue</category>
      <category>go</category>
      <category>community</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal: Documentation</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Sat, 01 Sep 2018 20:24:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-documentation-4nf3</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-documentation-4nf3</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTES:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;This is post 7 in a series; see &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-introduction-jl5"&gt;the introduction&lt;/a&gt; for all of them, and the requirements for which this software was built.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Links that start with the text “mpj:” are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have spent a lot of time looking at various aspects of the application, written from the perspective of a software developer. However, there is another perspective to consider - that of a user. With a “minimalist” app, how do we let them know what the buttons do?&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up GitHub Pages
&lt;/h2&gt;

&lt;p&gt;In other projects, we had use &lt;a href="https://pages.github.com"&gt;GitHub Pages&lt;/a&gt; by creating a &lt;code&gt;gh-pages&lt;/code&gt; branch of our repository. This has some advantages, such as a page structure that is completely separate from the source files. At the same time, though, you either have to have 2 sandboxes on different branches, or switch branches when switching from coding to documentation. Fortunately, this is not the only option; GitHub Pages can publish from a &lt;code&gt;/docs&lt;/code&gt; folder on the &lt;code&gt;master&lt;/code&gt; branch as well. They have a &lt;a href="https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch"&gt;nice guide on how to set it up&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;_config.yml&lt;/code&gt; file that’s also in the &lt;code&gt;/docs&lt;/code&gt; folder (&lt;a href="https://github.com/bit-badger/myPrayerJournal/tree/1.0.0/docs"&gt;mpj:docs folder&lt;/a&gt;) was put there by GitHub when we selected the theme from the Settings page of the repo. The only file we ever put there was &lt;code&gt;index.md&lt;/code&gt; (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/docs/index.md"&gt;mpj:docs/index.md&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the Documentation
&lt;/h2&gt;

&lt;p&gt;As GitHub Pages uses &lt;a href="https://jekyllrb.com"&gt;Jekyll&lt;/a&gt; behind the scenes, we have &lt;a href="https://daringfireball.net/projects/markdown/"&gt;Markdown&lt;/a&gt; and the default &lt;a href="https://github.com/Shopify/liquid/wiki"&gt;Liquid&lt;/a&gt; tags available to us. We didn’t need any Liquid tags, though, as the documentation is pretty straightforward. You may notice that there isn’t any front matter at the top of &lt;code&gt;index.md&lt;/code&gt;; absent that, GitHub Pages selects the title of the page from the first &lt;code&gt;h1&lt;/code&gt; tag in the document, defined with a leading &lt;code&gt;#&lt;/code&gt; sequence.&lt;/p&gt;

&lt;p&gt;If you browse the &lt;a href="https://github.com/bit-badger/myPrayerJournal/commits/1.0.0/docs/index.md"&gt;commit history for &lt;code&gt;index.md&lt;/code&gt;&lt;/a&gt;, you’ll see that many of the commits reference either a version or issue number. This makes it rather simple to go back in time through the source code, and not only review the code, but see how its functionality was explained in the documentation. You can also review typos that got committed, which helps keep you humble. :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Making It Better
&lt;/h2&gt;

&lt;p&gt;There is more that could be done to improve this aspect of the project. The first would be to assign it a subdomain of &lt;code&gt;prayerjournal.me&lt;/code&gt; instead of serving the pages from &lt;code&gt;bit-badger.github.io&lt;/code&gt;. GitHub Pages does support custom subdomains, and even supports HTTPS for these through &lt;a href="https://letsencrypt.org"&gt;Let’s Encrypt&lt;/a&gt;. The second would be to work up a lightweight theme for the page that matched the look-and-feel of the main site; this would make a more unified experience. Finally, while “minimalist” was the goal, there ended up being a few features that took lots of words to explain; a table of contents on the page would help people navigate directly to the help they need.&lt;/p&gt;

&lt;p&gt;Our tour is drawing to a close; next time, we’ll wrap it up with observations and opinions over the development process.&lt;/p&gt;

</description>
      <category>jekyll</category>
      <category>github</category>
      <category>documentation</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal: The Data Store</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Fri, 31 Aug 2018 22:48:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-data-store-5efa</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-data-store-5efa</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTES:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;This is post 6 in a series; see &lt;a href="/danieljsummers/a-tour-of-myprayerjournal-introduction-jl5"&gt;the introduction&lt;/a&gt; for all of them, and the requirements for which this software was built.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Links that start with the text “mpj:” are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Up to this point in our tour, we’ve talked about data a good bit, but it has all been in the context of whatever else we were discussing. Let’s dig into the data structure a bit, to see how our information is persisted and retrieved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conceptual Design
&lt;/h2&gt;

&lt;p&gt;The initial thought was to create a document store with one document type, the request. The request would have an ID, the ID of the user who created it, and an array of updates/records. Through the initial phases of development, our preferred document database (&lt;a href="https://rethinkdb.com"&gt;RethinkDB&lt;/a&gt;) was going through a tough period, with their company shutting down; thankfully, they’re now part of the Linux Foundation, so they’re still around. RethinkDB supports calculated fields in documents, so the plan was to have a few of those to keep us from having to retrieve or search through the array of updates.&lt;/p&gt;

&lt;p&gt;We also considered a similar design using &lt;a href="https://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt;‘s native JSON support. While it does not natively support calculated fields, a creative set of indexes could also suffice. As we thought it through a little more, though, this seemed to be over-engineering; this isn’t unstructured data, and PostgreSQL handles max-length character fields very well. (This is supposed to be a “minimalist” application, right?) A relational structure would fit our needs quite nicely.&lt;/p&gt;

&lt;p&gt;The starting design, then, used 2 tables. &lt;code&gt;request&lt;/code&gt; had an ID and a user ID; &lt;code&gt;history&lt;/code&gt; had the request ID, an “as of” date, a status (created, updated, etc.), and the optional text associated with that update. Early in development, the &lt;code&gt;journal&lt;/code&gt; view brought together the request/user IDs along with the latest history entry that affected the text of the request, as well as the last date/time an action had occurred on the request. When the notes capability was added, it got its own &lt;code&gt;note&lt;/code&gt; table; its structure was similar to the &lt;code&gt;history&lt;/code&gt; table, but with non-optional text and without a status. As snoozing and recurrence capabilities were added, those fields were added to the &lt;code&gt;request&lt;/code&gt; table (and the &lt;code&gt;journal&lt;/code&gt; view).&lt;/p&gt;

&lt;p&gt;The final design uses 3 tables, 2 of which have a one-to-many relationship with the third; and 1 view, which provides the calculated fields we had originally planned for RethinkDB to calculate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Changes (Migrations)
&lt;/h2&gt;

&lt;p&gt;As we ended up using 3 different server environments over the course of this project, we ended up writing a &lt;code&gt;DbContext&lt;/code&gt; class based on our existing structure. For the Node.js backend, we created a DDL file (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/3c3f0a7981fa8f82d3cc904630960ca43c910cd2/src/api/src/db/ddl.js"&gt;mpj:ddl.js&lt;/a&gt;, v0.8.4+) that checked for the existence of each table and view, and also had the SQL to execute if the check failed. For the Go version (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/d0ea7cf3c631512ea6b3afba61a25c83aaded6c8/src/api/data/data.go#L307"&gt;mpj:data.go&lt;/a&gt;, v0.9.6+), the &lt;code&gt;EnsureDB&lt;/code&gt; function does a similar thing; looking at line 347, it is checking for a specific column in the &lt;code&gt;request&lt;/code&gt; table, and running the &lt;code&gt;ALTER TABLE&lt;/code&gt; statement to add it if it isn’t there.&lt;/p&gt;

&lt;p&gt;The only change that was required since the F#/Giraffe backend has been in place was the one to support request recurrence. Since we did not end up with a scaffolded EF Core initial migration/model, we simply wrote a SQL script to accomplish these changes (&lt;a href="https://github.com/bit-badger/myPrayerJournal/tree/1.0.0/src/sql"&gt;mpj:sql directory&lt;/a&gt;).&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The EF Core Model
&lt;/h2&gt;

&lt;p&gt;EF Core uses the familiar &lt;code&gt;DbContext&lt;/code&gt; class from prior versions of Entity Framework. myPrayerJournal does take advantage of a feature that just arrived in EF Core 2.1, though - the &lt;code&gt;DbQuery&lt;/code&gt; type. &lt;code&gt;DbSet&lt;/code&gt;s are collections of entities that generally map to an underlying database table. They can be mapped to views, but unless it’s an updateable view, updating those entities results in a runtime error; plus, since they can’t be updated, there’s no need for the change tracking mechanism to care about the entities returned. &lt;code&gt;DbQuery&lt;/code&gt; addresses both these concerns, providing lightweight read-only access to data from views.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DbContext&lt;/code&gt; class is defined in Data.fs (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Data.fs"&gt;mpj:Data.fs&lt;/a&gt;), starting in line 189. It’s relatively straightforward, though if you have only ever seen a C# model, it’s a bit different. The combination of &lt;code&gt;val mutable x : [type]&lt;/code&gt; and the &lt;code&gt;[&amp;lt;DefaultValue&amp;gt;]&lt;/code&gt; attribute are the F# equivalent of C#’s &lt;code&gt;[type] x;&lt;/code&gt; declaration, which creates a variable and initializes reference types to &lt;code&gt;null&lt;/code&gt;. The EF Core runtime provides these instances to their setters (lines 203, 206, 209, and 212), and the application code uses them via the getters (a line earlier, each).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OnModelCreating&lt;/code&gt; overridden method (line 214) is called when the runtime first creates its instance of the data model. Within this method, we call the &lt;code&gt;.configureEF&lt;/code&gt; function of each of our database types. The name of this function isn’t prescribed, and we could define the entire model without even referencing the data types of our entities; however, this technique gives us a “configure where it’s defined” paradigm with each entity type. While the EF “Code First” model creates tables that don’t need a lot of configuring, we must provide more information about the layout of the database tables since we’re writing a &lt;code&gt;DbContext&lt;/code&gt; to target an existing database.&lt;/p&gt;

&lt;p&gt;Let’s start out by taking a look at &lt;code&gt;History.configureEF&lt;/code&gt; (line 50). Line 53 says that we’re going to the table &lt;code&gt;history&lt;/code&gt;. This seems to be a no-brainer, but EF Core would (by convention) be expecting a &lt;code&gt;History&lt;/code&gt; table; since PostgreSQL uses a different syntax for case-sensitive names, these queries would look like &lt;code&gt;SELECT ... FROM "History" ...&lt;/code&gt;, resulting in a nice “relation does not exist” error. Line 54 defines our compound key (&lt;code&gt;requestId&lt;/code&gt; and &lt;code&gt;asOf&lt;/code&gt;). Lines 55-57 define certain properties of the entity as required; if we try to store an entity where these fields are not set, the runtime will raise an exception before even trying to take it to the database. &lt;em&gt;(F#’s non-nullability makes this a non-issue, but it still needs to be defined to match the database.)&lt;/em&gt; Line 58 may seem to do nothing, but what it does is make the &lt;code&gt;text&lt;/code&gt; property immediately visible to the model builder; then, we can define an &lt;code&gt;OptionConverter&amp;lt;string&amp;gt;&lt;/code&gt;&lt;sup&gt;2&lt;/sup&gt; for it, which will translate between &lt;code&gt;null&lt;/code&gt; and &lt;code&gt;string option&lt;/code&gt; (&lt;code&gt;None&lt;/code&gt; = &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;Some [x]&lt;/code&gt; = &lt;code&gt;[x]&lt;/code&gt;). &lt;em&gt;(Lines 60-61 are left over from when I was trying to figure out why line 62 was raising an exception, leading to the addition of line 58; they could safely be removed, and will be for a post-1.0 release.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;History&lt;/code&gt; is the most complex configuration, but let’s take a peek at &lt;code&gt;Request.configureEF&lt;/code&gt; (line 126) to see one more interesting technique. Lines 107-110 define the &lt;code&gt;history&lt;/code&gt; and &lt;code&gt;notes&lt;/code&gt; collections on the &lt;code&gt;Request&lt;/code&gt; type; lines 138-145 define the one-to-many relationship (without a foreign key entity in the child types). Note the casts to &lt;code&gt;IEnumerable&amp;lt;x&amp;gt;&lt;/code&gt; (lines 138 and 142) and &lt;code&gt;obj&lt;/code&gt; (lines 140 and 144); while F# is good about inferring types in a lot of cases, these functions are two places it is not. We can use the &lt;code&gt;:&amp;gt;&lt;/code&gt; operator for the cast, because these types are part of the inheritance chain. &lt;em&gt;(The &lt;code&gt;:?&amp;gt;&lt;/code&gt; operator is used for potentially unsafe casts.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finally, the attributes above each record type need a bit of explanation; each one has &lt;code&gt;[&amp;lt;CLIMutable; NoComparison; NoEquality&amp;gt;]&lt;/code&gt;. The &lt;code&gt;CLIMutable&lt;/code&gt; attribute creates a no-argument constructor for the record type, which the runtime can use to create instances of the type. (The side effect is that we may get null instances of what is expected to be a non-null type, but we’ll look at dealing with that a bit later.) The &lt;code&gt;NoComparison&lt;/code&gt; and &lt;code&gt;NoEquality&lt;/code&gt; attributes keep F# from creating field-level equality and comparison methods on the types. While these are normally helpful, there is an edge case where they can raise &lt;code&gt;NullReferenceException&lt;/code&gt;s, especially when used on null instances. As these record types are simply our data transfer objects (both from SQL and to JSON), we don’t need the functionality anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading and Writing Data
&lt;/h2&gt;

&lt;p&gt;EF Core uses the “unit of work” pattern with its &lt;code&gt;DbContext&lt;/code&gt; class. Each instance maintains knowledge of the entities it’s loaded, and does change tracking against those entities, so it knows what commands to issue when &lt;code&gt;.SaveChanges()&lt;/code&gt; (or &lt;code&gt;.SaveChangesAsync()&lt;/code&gt;) is called. It doesn’t do this for free, though, and while EF Core does this much more efficiently than Entity Framework proper, F# record types do not support mutation; if &lt;code&gt;req&lt;/code&gt; is a &lt;code&gt;Request&lt;/code&gt; instance, for example, &lt;code&gt;{ req with showAfter = 123456789L }&lt;/code&gt; returns a &lt;strong&gt;new&lt;/strong&gt; &lt;code&gt;Request&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;This is the problem whose solution is enabled by lines 227-233 in Data.fs. We can manually register an instance of an entity as either added or modified, and when we call &lt;code&gt;.SaveChanges()&lt;/code&gt;, the runtime will generate the SQL to update the data store accordingly. This also allows us to use &lt;code&gt;.AsNoTracking()&lt;/code&gt; in our queries (lines 250, 258, 265, and 275), which means that the resultant entities will not be registered with the change tracker, saving that overhead. Notice that we don’t specify that on line 243; since &lt;code&gt;Journal&lt;/code&gt; is defined as a &lt;code&gt;DbQuery&lt;/code&gt; instead of a &lt;code&gt;DbSet&lt;/code&gt;, we get change-tracking-avoidance for free.&lt;/p&gt;

&lt;p&gt;Generally speaking, the preferred method of writing queries against a &lt;code&gt;DbContext&lt;/code&gt; instance is to define extension methods against it. These are &lt;code&gt;static&lt;/code&gt; by default, and they enable the context to be as lightweight as possible, while extending it when necessary. However, since this context is so small, we’ve created 6 methods on the context that we use to obtain data.&lt;/p&gt;

&lt;p&gt;If you’ve been reading along with the tour, we have already seen a few API handler functions (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Handlers.fs"&gt;mpj:Handlers.fs&lt;/a&gt;) that use the data context. Line 137 has the handler for &lt;code&gt;/api/journal&lt;/code&gt;, the endpoint to retrieve a user’s active requests. It uses &lt;code&gt;.JournalByUserId()&lt;/code&gt;, defined in Data.fs line 242, whose signature is &lt;code&gt;string -&amp;gt; JournalRequest seq&lt;/code&gt;. (The latter is an F# alias for &lt;code&gt;IEnumerable&amp;lt;JournalRequest&amp;gt;&lt;/code&gt;.) Back in the handler, we use &lt;code&gt;db ctx&lt;/code&gt; to get the context (more on that below), then call the method; we’re piping the output of &lt;code&gt;userId ctx&lt;/code&gt; into it, so it gets its lone parameter from the pipe, then its output is piped to the &lt;code&gt;asJson&lt;/code&gt; function we discussed &lt;a href="/danieljsummers/a-tour-of-myprayerjournal-the-api-56l5"&gt;as part of the API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Line 192, the handler for &lt;code&gt;/api/request/[id]/history&lt;/code&gt;, demonstrates both inserting and updating data. We attempt to retrieve the request by its ID and the user ID; if that fails, we return a 404. If it succeeds, though, we add a history entry (lines 201-207), and optionally update the &lt;code&gt;showAfter&lt;/code&gt; field of the request based on its recurrence. Finally, the call on line 212 commits the changes for this particular instance. Since the &lt;code&gt;.SaveChanges[Async]()&lt;/code&gt; methods return the number of records affected, we cannot use the &lt;code&gt;do!&lt;/code&gt; operator for this; F# makes you explicitly ignore values you aren’t either returning or assigning to a name. However, defining &lt;code&gt;_&lt;/code&gt; as a parameter or name demonstrates that we realize there is a value to be had, we just are not going to do anything with it.&lt;/p&gt;

&lt;p&gt;We mentioned that &lt;code&gt;CLIMutable&lt;/code&gt; record types could be null. Since record types cannot normally be null, we cannot code something like &lt;code&gt;match [var] with null -&amp;gt; ...&lt;/code&gt;; it’s a compiler syntax error. What we can do, though, is use the &lt;code&gt;box&lt;/code&gt; operator. &lt;code&gt;box&lt;/code&gt; “boxes” whatever value we have into an object container, where we can then check it against &lt;code&gt;null&lt;/code&gt;. The function &lt;code&gt;toOption&lt;/code&gt; in Data.fs on line 11 does this work for us; throughout the retrieval methods, we use it to return &lt;code&gt;option&lt;/code&gt;s for items that are either present or absent. This is why we could do the &lt;code&gt;match&lt;/code&gt; statement in the &lt;code&gt;/api/request/[id]/history&lt;/code&gt; handler against &lt;code&gt;Some&lt;/code&gt; and &lt;code&gt;None&lt;/code&gt; values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting a &lt;code&gt;DbContext&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Since Giraffe sits atop ASP.NET Core, we use the same technique; we use the &lt;code&gt;.AddDbContext()&lt;/code&gt; extension method on the &lt;code&gt;IServiceCollection&lt;/code&gt; interface, and assign it when we set up the dependency injection container. In our case, it’s in Program.fs (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Program.fs"&gt;mpj:Program.fs&lt;/a&gt;) line 50, where we also direct it to use a PostgreSQL connection defined by the connection string “mpj”. (This comes from the unified configuration built from &lt;code&gt;appsettings.json&lt;/code&gt; and &lt;code&gt;appsettings.[Environment].json&lt;/code&gt;.) If we look back at Handlers.fs, lines 45-47, we see the definition of the &lt;code&gt;db ctx&lt;/code&gt; call we used earlier. We’re using the Giraffe-provided &lt;code&gt;GetService&amp;lt;'T&amp;gt;()&lt;/code&gt; extension method to return this instance.&lt;/p&gt;

&lt;p&gt;Our tour is nearing its end, but we still have a few stops to go. Next time, we’ll look at &lt;a href="/danieljsummers/a-tour-of-myprayerjournal-documentation-4nf3"&gt;how we generated documentation&lt;/a&gt; to tell people how to use this app.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;Writing this post has shown me that I need to either create a SQL creation script for the repo, or create an EF Core initial migration/model, so the database ever has to be recreated from scratch. It’s good to write about things after you do them!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;This is also a package I wrote; it’s &lt;a href="https://www.nuget.org/packages/FSharp.EFCore.OptionConverter/"&gt;available on NuGet&lt;/a&gt;, and I also wrote &lt;a href="https://dev.to/danieljsummers/f-options-with-ef-core-417a"&gt;a post about what it does&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>efcore</category>
      <category>database</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal: Authentication</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Thu, 30 Aug 2018 09:30:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-authentication-343e</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-authentication-343e</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTES:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;This is post 5 in a series; see &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-introduction-jl5"&gt;the introduction&lt;/a&gt; for all of them, and the requirements for which this software was built.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Links that start with the text “mpj:” are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point in our tour, we’re going to shift to a cross-cutting concern for both app and API - authentication. While authentication and authorization are distinct concerns, the authorization check in myPrayerJournal is simply “Are you authenticated?” So, while we’ll touch on authorization, and it will seem like a synonym for authentication, remember that they would not be so in a more complex application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deciding on Auth0
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://auth0.com"&gt;Auth0&lt;/a&gt; provides authentication services; they focus on one thing, and getting that one thing right. They support simple username/password authentication, as well as integrations with many other providers. As “minimalist” was one of our goals, not having to build yet another user system was appealing. As an open source project, Auth0 provides these services at no cost. They’re the organization behind the &lt;a href="https://jwt.io"&gt;JSON Web Token (JWT)&lt;/a&gt; standard, which allows base-64-encoded, encrypted JSON to be passed around as proof of identity.&lt;/p&gt;

&lt;p&gt;This decision has proved to be a good one. In the introduction, we mentioned all of the different frameworks and server technologies we had used before settling on the one we did. In all but one of these “roads not further traveled”&lt;sup&gt;1&lt;/sup&gt;, authentication worked. They have several options for how to use their service; you can bring in their library and host it yourself, you can write your own and make your own calls to their endpoints, or you can use their hosted version. We opted for the latter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating Auth0 in the App
&lt;/h2&gt;

&lt;p&gt;JavaScript seems to be Auth0’s primary language. They provide an &lt;a href="https://www.npmjs.com/package/auth0-js"&gt;npm package&lt;/a&gt; to support using the responses that will be returned from their hosted login page. The basic flow is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user clicks a link that executes Auth0’s &lt;code&gt;authorize()&lt;/code&gt; function&lt;/li&gt;
&lt;li&gt;The user completes authorization through Auth0&lt;/li&gt;
&lt;li&gt;Auth0 returns the result and JWT to a predefined endpoint in the app&lt;/li&gt;
&lt;li&gt;The app uses Auth0’s &lt;code&gt;parseHash()&lt;/code&gt; function to extract the JWT from the URL (a &lt;code&gt;GET&lt;/code&gt; request)&lt;/li&gt;
&lt;li&gt;If everything is good, establish the user’s session and proceed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;myPrayerJournal’s implementation is contained in &lt;code&gt;AuthService.js&lt;/code&gt; (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/auth/AuthService.js"&gt;mpj:AuthService.js&lt;/a&gt;). There is a file that is not part of the source code repository; this is the file that contains the configuration variables for the Auth0 instance. Using these variables, we configure the &lt;code&gt;WebAuth&lt;/code&gt; instance from the Auth0 package; this instance becomes the execution point for our other authentication calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using JWTs in the App
&lt;/h2&gt;

&lt;p&gt;We’ll start easy. The &lt;code&gt;login()&lt;/code&gt; function simply exposes Auth0’s &lt;code&gt;authorize()&lt;/code&gt; function, which directs the user to the hosted log on page.&lt;/p&gt;

&lt;p&gt;The next in logical sequence, &lt;code&gt;handleAuthentication()&lt;/code&gt;, is called by &lt;code&gt;LogOn.vue&lt;/code&gt; (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/components/user/LogOn.vue"&gt;mpj:LogOn.vue&lt;/a&gt;) on line 16, passing in our store and the router. (In our &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-api-56l5"&gt;last post&lt;/a&gt;, we discussed how server requests to a URL handled by the app should simply return the app, so that it can process the request; this is one of those cases.) &lt;code&gt;handleAuthentication()&lt;/code&gt; does several things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It calls &lt;code&gt;parseHash()&lt;/code&gt; to extract the JWT from the request’s query string.&lt;/li&gt;
&lt;li&gt;If we got both an access token and an ID token:

&lt;ul&gt;
&lt;li&gt;It calls &lt;code&gt;setSession()&lt;/code&gt;, which saves these to local storage, and schedules renewal (which we’ll discuss more in a bit).&lt;/li&gt;
&lt;li&gt;It then calls Auth0’s &lt;code&gt;userInfo()&lt;/code&gt; function to retrieve the user profile for the token we just received.&lt;/li&gt;
&lt;li&gt;When that comes back, it calls the store’s (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/store/index.js"&gt;mpj:store/index.js&lt;/a&gt;) &lt;code&gt;USER_LOGGED_ON&lt;/code&gt; mutation, passing the user profile; the mutation saves the profile to the store, local storage, and sets the &lt;code&gt;Bearer&lt;/code&gt; token on the API service (more on that below as well).&lt;/li&gt;
&lt;li&gt;Finally, it replaces the current location (&lt;code&gt;/user/log-on?[lots-of-base64-stuff]&lt;/code&gt;) with the URL &lt;code&gt;/journal&lt;/code&gt;; this navigates the user to their journal.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;If something didn’t go right, we log to the console and pop up an alert. There may be a more elegant way to handle this, but in testing, the only way to reliably make this pop up was to mess with things behind the scenes. (And, if people do that, they’re not entitled to nice error messages.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s dive into the store’s &lt;code&gt;USER_LOGGED_ON&lt;/code&gt; mutation a bit more; it starts on line 68. The local storage item and the state mutations are pretty straightforward, but what about that &lt;code&gt;api.setBearer()&lt;/code&gt; call? The API service (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/api/index.js"&gt;mpj:api/index.js&lt;/a&gt;) handles all the API calls through the &lt;a href="https://www.npmjs.com/package/axios"&gt;Axios&lt;/a&gt; library. Axios supports defining default headers that should be sent with every request, and we’ll use the HTTP &lt;code&gt;Authorization: Bearer [base64-jwt]&lt;/code&gt; header to tell the API what user is logged in. Line 18 sets the default &lt;code&gt;authorization&lt;/code&gt; header to use for all future requests. (Back in the store, note that the &lt;code&gt;USER_LOGGED_OFF&lt;/code&gt; mutation (just above this) does the opposite; it clears the &lt;code&gt;authorization&lt;/code&gt; header. The &lt;code&gt;logout()&lt;/code&gt; function in &lt;code&gt;AuthService.js&lt;/code&gt; clears the local storage.)&lt;/p&gt;

&lt;p&gt;At this point, once the user is logged in, the &lt;code&gt;Bearer&lt;/code&gt; token is sent with every API call. None of the components, nor the store or its actions, need to do anything differently; it just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maintaining Authentication
&lt;/h2&gt;

&lt;p&gt;JWTs have short expirations, usually expressed in hours. Having a user’s authentication go stale is not good! The &lt;code&gt;scheduleRenewal()&lt;/code&gt; function in &lt;code&gt;AuthService.js&lt;/code&gt; schedules a behind-the-scenes renewal of the JWT. When the time for renewal arrives, &lt;code&gt;renewToken()&lt;/code&gt; is called, and if the renewal is successful, it runs the result through &lt;code&gt;setSession()&lt;/code&gt;, just as we did above, which schedules the next renewal as its last step.&lt;/p&gt;

&lt;p&gt;For this to work, we had to add &lt;code&gt;/static/silent.html&lt;/code&gt; as an authorized callback for Auth0. This is an HTML page that sits outside of the Vue app; however, the &lt;code&gt;usePostMessage: true&lt;/code&gt; parameter tells the renewal call that it will receive its result from a &lt;code&gt;postMessage&lt;/code&gt; call. &lt;code&gt;silent.html&lt;/code&gt; uses the Auth0 library to parse the hash and post the result to the parent window.&lt;sup&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using JWTs in the API
&lt;/h2&gt;

&lt;p&gt;Now that we’re sending a &lt;code&gt;Bearer&lt;/code&gt; token to the API, the API can tell if a user is logged in. We looked at some of the handlers that help us do that when we looked at the API in depth. Let’s return to those and see how that is.&lt;/p&gt;

&lt;p&gt;Before we look at the handlers, though, we need to look at the configuration, contained in &lt;code&gt;Program.fs&lt;/code&gt; (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Program.fs"&gt;mpj:Program.fs&lt;/a&gt;). You may remember that Giraffe sits atop ASP.NET Core; we can utilize its &lt;code&gt;JwtBearer&lt;/code&gt; methods to set everything up. Lines 38-48 are the interesting ones for us; we use the &lt;code&gt;UseAuthentication&lt;/code&gt; extension method to set up JWT handling, then use the &lt;code&gt;AddJwtBearer&lt;/code&gt; extension method to configure our specific JWT values. (As with the app, these are part of a file that is not in the repository.) The end result of this configuration is that, if there is a &lt;code&gt;Bearer&lt;/code&gt; token that is a valid JWT, the &lt;code&gt;User&lt;/code&gt; property of the &lt;code&gt;HttpContext&lt;/code&gt; has an instance of the &lt;code&gt;ClaimsPrincipal&lt;/code&gt; type, and the various properties from the JWT’s payload are registered as &lt;code&gt;Claims&lt;/code&gt; on that user.&lt;/p&gt;

&lt;p&gt;Now we can turn our attention to the handlers (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Handlers.fs"&gt;mpj:Handlers.fs&lt;/a&gt;). &lt;code&gt;authorize&lt;/code&gt;, on line 72, calls &lt;code&gt;user ctx&lt;/code&gt;, which is defined on lines 50-51. All this does is look for a claim of the type &lt;code&gt;ClaimTypes.NameIdentifier&lt;/code&gt;. This can be non-intuitive, as the source for this is the &lt;code&gt;sub&lt;/code&gt; property from the JWT&lt;sup&gt;3&lt;/sup&gt;. A valid JWT with a &lt;code&gt;sub&lt;/code&gt; claim is how we tell we have a logged on user; an authenticated user is considered authorized.&lt;/p&gt;

&lt;p&gt;You may have noticed that, when we were describing the entities for the API, we did not mention a &lt;code&gt;User&lt;/code&gt; type. The reason for that is simple; the only user information it stores is the &lt;code&gt;sub&lt;/code&gt;. &lt;code&gt;Request&lt;/code&gt;s are assigned by user ID, and the user ID is included with every attempt to make any change to a request. This eliminates URL hacking or rogue API posting being able to get anything meaningful from the API.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;userId&lt;/code&gt; function, just below the &lt;code&gt;user&lt;/code&gt; function, extracts this claim and returns its value, and it’s used throughout the remainder of &lt;code&gt;Handlers.fs&lt;/code&gt;. &lt;code&gt;add&lt;/code&gt; (line 160) uses it to set the user ID for a new request. &lt;code&gt;addHistory&lt;/code&gt; (line 192) and &lt;code&gt;addNote&lt;/code&gt; (line 218) both use the user ID, as well as the passed request ID, to try to retrieve the request before adding history or notes to it. &lt;code&gt;journal&lt;/code&gt; (line 137) uses it to retrieve the journal by user ID.&lt;/p&gt;

&lt;p&gt;We now have a complete application, with the same user session providing access to the Vue app and tying all API calls to that user. We also use it to maintain data security among users, while truly outsourcing all user data to Microsoft or Google (the two external providers currently registered). We do still have a few more stops on our tour, though; the next is the back end data store.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;Sorry, Elm; it’s not you, it’s me…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;This does work, but not indefinitely; if I leave the same browser window open from the previous day, I still have to sign in again. I very well could be “doing it wrong;” this is an area where I probably experienced the most learning through creating this project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;I won’t share how long it took me to figure out that &lt;code&gt;sub&lt;/code&gt; mapped to that; let’s just categorize it as “too long.” In my testing, it’s the only claim that doesn’t come across by its JWT name.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>fsharp</category>
      <category>authentication</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal: The API</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Wed, 29 Aug 2018 09:37:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-api-56l5</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-api-56l5</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTES:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;This is post 4 in a series; see &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-introduction-jl5"&gt;the introduction&lt;/a&gt; for all of them, and the requirements for which this software was built.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Links that start with the text “mpj:” are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have a wonderful, shiny, &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-state-in-the-browser-95o"&gt;reactive&lt;/a&gt; &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-front-end-4589"&gt;front end&lt;/a&gt;, we need to be able to get some data into it. We’ll be communicating via JSON between the app and the server. In this post, we’ll also attempt to explain some about the F# language features used as part of the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data
&lt;/h2&gt;

&lt;p&gt;The entities are defined in Data.fs (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Data.fs"&gt;mpj:Data.fs&lt;/a&gt;). We’ll dig into them more fully in &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-data-store-5efa"&gt;the “data store” post&lt;/a&gt;, but for now, we’ll just focus on the types and fields. We have four types: &lt;code&gt;History&lt;/code&gt; (lines 33-39), &lt;code&gt;Note&lt;/code&gt; (lines 67-71), &lt;code&gt;Request&lt;/code&gt; (lines 94-110), and &lt;code&gt;JournalRequest&lt;/code&gt; (lines 153-173). A &lt;code&gt;Request&lt;/code&gt; can have multiple &lt;code&gt;Note&lt;/code&gt;s and &lt;code&gt;History&lt;/code&gt; entries, and &lt;code&gt;JournalRequest&lt;/code&gt; is based on a view that pulls these together and computes things like the current text of the request and when it should be displayed again.&lt;/p&gt;

&lt;p&gt;We apply no special JSON transformations, so the fields in these record types are the properties in the exported JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  The URLs
&lt;/h2&gt;

&lt;p&gt;To set the API apart from the rest of the URLs, they all start with &lt;code&gt;/api/&lt;/code&gt;. Request URLs generally follow the form &lt;code&gt;request/[id]/[action]&lt;/code&gt;, and there is a separate URL for the journal. Line 54 in &lt;code&gt;Program.fs&lt;/code&gt; (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Program.fs"&gt;mpj:Program.fs&lt;/a&gt;) has the definition of the routes. We used &lt;a href="https://github.com/giraffe-fsharp/Giraffe"&gt;Giraffe&lt;/a&gt;‘s &lt;a href="https://github.com/giraffe-fsharp/Giraffe.TokenRouter"&gt;Token Router&lt;/a&gt; instead of the traditional one, as we didn’t need to support any URL schemes it doesn’t. The result really looks like a nice, clean “table of contents” for the routes support by the API.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;We aren’t done with routes just yet, though. Let’s take a look at that &lt;code&gt;notFound&lt;/code&gt; handler (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/api/MyPrayerJournal.Api/Handlers.fs"&gt;mpj:Handlers.fs&lt;/a&gt;); it’s on line 27. Since we’re serving a SPA, we need to return &lt;code&gt;index.html&lt;/code&gt;, the entry point of the SPA, for URLs that belong to it. Picture a user sitting at &lt;code&gt;https://prayerjournal.me/journal&lt;/code&gt; and pressing “Refresh;” we don’t want to return a 404! Since the app has a finite set of URL prefixes, we’ll check to see if one of those is the URL. If it is, we send the Vue app; if not, we send a 404 response. This way, we can return true 404 responses for the inevitable hacking attempts we’ll receive (pro tip, hackers - &lt;code&gt;/wp-admin/wp-upload.php&lt;/code&gt; does not exist).&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Handlers
&lt;/h2&gt;

&lt;p&gt;Giraffe uses the term “handler” to define a function that handles a request. Handlers have the signature &lt;code&gt;HttpFunc -&amp;gt; HttpContext -&amp;gt; Task&amp;lt;HttpContext option&amp;gt;&lt;/code&gt; (aliased as &lt;code&gt;HttpHandler&lt;/code&gt;), and can be composed via the &lt;code&gt;&amp;gt;=&amp;gt;&lt;/code&gt; (“fish”) operator. The &lt;code&gt;option&lt;/code&gt; part in the signature is the key in composing handler functions. The &lt;code&gt;&amp;gt;=&amp;gt;&lt;/code&gt; operator creates a pipeline that sends the output of one function into the input of another; however, if a function fails to return a &lt;code&gt;Some&lt;/code&gt; option for the &lt;code&gt;HttpContext&lt;/code&gt; parameter, it short-circuits the remaining logic.&lt;sup&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The biggest use of that composition in myPrayerJournal is determining if a user is logged in or not. Authorization is also getting &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-authentication-343e"&gt;its own post&lt;/a&gt;, so we’ll just focus on the yes/no answer here. The &lt;code&gt;authorized&lt;/code&gt; handler (line 71) looks for the presence of a user. If it’s there, it returns &lt;code&gt;next ctx&lt;/code&gt;, where &lt;code&gt;next&lt;/code&gt; is the next &lt;code&gt;HttpFunc&lt;/code&gt; and &lt;code&gt;ctx&lt;/code&gt; is the &lt;code&gt;HttpContext&lt;/code&gt; it received; this results in a &lt;code&gt;Task&amp;lt;HttpContext option&amp;gt;&lt;/code&gt; which continues to process, hopefully following the happy path and eventually returning &lt;code&gt;Some&lt;/code&gt;. If the user is not there, though, it returns the &lt;code&gt;notAuthorized&lt;/code&gt; handler, also passing &lt;code&gt;next&lt;/code&gt; and &lt;code&gt;ctx&lt;/code&gt;; however, if we look up to line 67 and the definition of the &lt;code&gt;notAuthorized&lt;/code&gt; handler, we see that it ignores both &lt;code&gt;next&lt;/code&gt; and &lt;code&gt;ctx&lt;/code&gt;, and returns &lt;code&gt;None&lt;/code&gt;. However, notice that this handler has some fish composition in it; &lt;code&gt;setStatusCode&lt;/code&gt; returns &lt;code&gt;Some&lt;/code&gt; (it has succeeded) but we short-circuit the pipeline immediately thereafter.&lt;/p&gt;

&lt;p&gt;We can see this in use in the handler for the &lt;code&gt;/api/journal&lt;/code&gt; endpoint, starting on line 137. Both &lt;code&gt;authorize&lt;/code&gt; and the inline function below it have the &lt;code&gt;HttpHandler&lt;/code&gt; signature, so we can compose them with the &lt;code&gt;&amp;gt;=&amp;gt;&lt;/code&gt; operator. If a user is signed in, they get a journal; if not, they get a 403.&lt;/p&gt;

&lt;p&gt;When a handler is expecting a parameter from a route, the handler’s signature is a bit different. The handler for &lt;code&gt;/api/request/[id]&lt;/code&gt;, on line 246, has an extra parameter, &lt;code&gt;reqId&lt;/code&gt;. If we look back in &lt;code&gt;Program.fs&lt;/code&gt; line 64, we see where this handler is assigned its route; and, if you compare it to the route for &lt;code&gt;/api/journal&lt;/code&gt; on line 59, you’ll see that it looks the same. The difference here is the expectations of &lt;code&gt;route&lt;/code&gt; (for the journal) vs. &lt;code&gt;routef&lt;/code&gt; (for the request). &lt;code&gt;route&lt;/code&gt; expects no parameter, but &lt;code&gt;routef&lt;/code&gt; will extract the parameters, &lt;code&gt;Printf&lt;/code&gt; style, and pass them as parameters that precede the &lt;code&gt;HttpHandler&lt;/code&gt; signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Executing the Handlers
&lt;/h2&gt;

&lt;p&gt;myPrayerJournal has &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, and &lt;code&gt;PATCH&lt;/code&gt; routes defined. We’ll look at representative examples of each of these over the next few paragraphs.&lt;/p&gt;

&lt;p&gt;For our &lt;code&gt;GET&lt;/code&gt; example, let’s return to the &lt;code&gt;Request.get&lt;/code&gt; handler, starting on line 246. Once we clear the &lt;code&gt;authorize&lt;/code&gt; hurdle, the code attempts to retrieve a &lt;code&gt;JournalRequest&lt;/code&gt; by the request ID passed to the handler and the user ID passed as part of the request. If it exists, we return the JSON representation of it; if not, we return a 404. Note the order of parameters to the &lt;code&gt;json&lt;/code&gt; result handler - it’s &lt;code&gt;json [object] next ctx&lt;/code&gt; (or &lt;code&gt;json [object] HttpHandler&lt;/code&gt;). We also defined an &lt;code&gt;asJson&lt;/code&gt; function on line 75; this flips the parameters so that the output object is the last parameter (&lt;code&gt;asJson next ctx [object]&lt;/code&gt;), enabling the flow seen in the &lt;code&gt;journal&lt;/code&gt; handler on line 137.&lt;/p&gt;

&lt;p&gt;For our &lt;code&gt;POST&lt;/code&gt; example, we’ll use &lt;code&gt;Request.addNote&lt;/code&gt;, starting on line 218. It checks authorization, and to make sure the request exists for the current user. If everything is as it should be, it then uses Giraffe’s &lt;code&gt;BindJsonAsync&amp;lt;'T&amp;gt;&lt;/code&gt; extension method to create an instance of the expected input form. Since the handler doesn’t have a place to specify the expected input object, this type of model binding cannot happen automatically; the upside is, you don’t waste CPU cycles trying to do it if you don’t need it. Once we have our model bound, we create a new &lt;code&gt;Note&lt;/code&gt;, add it, then return a 201 response.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PATCH&lt;/code&gt; handlers are very similar; &lt;code&gt;Request.snooze&lt;/code&gt; is one such handler, starting on line 291. The flow is very similar as the one for &lt;code&gt;Request.addNote&lt;/code&gt;, except that we’re updating instead of adding, and we return 204 instead of 201.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the Web Server
&lt;/h2&gt;

&lt;p&gt;Giraffe enables &lt;a href="https://suave.io"&gt;Suave&lt;/a&gt;-like function composition on top of Kestrel and the ASP.NET Core infrastructure. Rather than using the &lt;code&gt;Startup&lt;/code&gt; class, myPrayerJournal uses a functional configuration strategy. The calls are in &lt;code&gt;Program.fs&lt;/code&gt; starting on line 115; there are lots of other guides on how to configure ASP.NET Core, so I won’t say too much more about it.&lt;/p&gt;

&lt;p&gt;Notice, though, line 31. Earlier, we discussed the &lt;code&gt;&amp;gt;=&amp;gt;&lt;/code&gt; operator and how it worked. This is an example of the &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; composition operator, which is part of F#. For functions whose output can be run directly into the next function’s input, the &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; operator allows those functions to be composed into a single function. If we were to look at the signature of the composed function within the parentheses, its signature would be &lt;code&gt;string -&amp;gt; unit&lt;/code&gt;; so, we pass the string “Kestrel” to it, and it runs through and returns &lt;code&gt;unit&lt;/code&gt;, which is what we expect for a configuration function. Here’s how that breaks down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctx.Configuration.GetSection&lt;/code&gt;‘s signature is &lt;code&gt;string -&amp;gt; IConfigurationSection&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;opts.Configure&lt;/code&gt;‘s signature is &lt;code&gt;IConfiguration -&amp;gt; KestrelConfigurationLoader&lt;/code&gt; (&lt;code&gt;IConfigurationSection&lt;/code&gt; implements &lt;code&gt;IConfiguration&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ignore&lt;/code&gt;‘s signature is &lt;code&gt;'a -&amp;gt; unit&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this still doesn’t make sense, perhaps this will help. The &lt;code&gt;Configure.kestrel&lt;/code&gt; function could also have been written using the &lt;code&gt;|&amp;gt;&lt;/code&gt; piping operator instead, with the equivalent code looking like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;kestrel&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebHostBuilderContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;KestrelServerOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetSection&lt;/span&gt; &lt;span class="s2"&gt;"Kestrel"&lt;/span&gt;
  &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Configure&lt;/span&gt;
  &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That concludes our tour of the API for now, though we’ll be looking at it again next time, when we take a deeper dive into &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-authentication-343e"&gt;authentication and authorization using Auth0&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;While we tried to follow REST principles in large part, the REST purists would probably say that it’s not quite RESTful enough to claim the name. But, hey, we do use &lt;code&gt;PATCH&lt;/code&gt;, so maybe we’ll get partial credit…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;Scott Wlaschin has a great post entitled &lt;a href="https://fsharpforfunandprofit.com/posts/recipe-part2/"&gt;“Railway Oriented Programming”&lt;/a&gt; that explains this concept in general, and &lt;a href="https://fsharpforfunandprofit.com/posts/recipe-part2/#an-alternative-to-bind"&gt;the fish operator&lt;/a&gt; specifically. Translating his definition to Giraffe’s handlers, the first function is &lt;code&gt;switch1&lt;/code&gt;, the &lt;code&gt;next&lt;/code&gt; parameter is &lt;code&gt;switch2&lt;/code&gt;, and the &lt;code&gt;HttpContext&lt;/code&gt; is the &lt;code&gt;x&lt;/code&gt; parameter; instead of &lt;code&gt;Success&lt;/code&gt; and &lt;code&gt;Failure&lt;/code&gt;, the return type utilizes the either/or nature of an option being &lt;code&gt;Some&lt;/code&gt; or &lt;code&gt;None&lt;/code&gt;. If you want to understand what makes F# such a great programming model, you’ll spend more time on his site than on The Bit Badger Blog.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>giraffe</category>
      <category>api</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A Tour of myPrayerJournal: State in the Browser</title>
      <dc:creator>Daniel J. Summers</dc:creator>
      <pubDate>Sun, 26 Aug 2018 07:30:00 +0000</pubDate>
      <link>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-state-in-the-browser-95o</link>
      <guid>https://dev.to/danieljsummers/a-tour-of-myprayerjournal-state-in-the-browser-95o</guid>
      <description>&lt;p&gt;&lt;em&gt;NOTES:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;This is post 3 in a series; see &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-introduction-jl5"&gt;the introduction&lt;/a&gt; for all of them, and the requirements for which this software was built.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Links that start with the text “mpj:” are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Flux (a pattern that originated at Facebook) defines state, as well as actions that can mutate that state. Redux is the most popular implementation of that pattern, and naturally works very well with React. However, other JavaScript framewoks use this pattern, as it ensures that state is managed sanely. (Well, the state is sane, but so is the developer!)&lt;/p&gt;

&lt;p&gt;As part of Vue, the &lt;a href="https://vuex.vuejs.org"&gt;Vuex&lt;/a&gt; component is a flux implementation for Vue that provides a standard way of managing state. They explain it in much more detail, so if the concept is a new one, you may want to read their “What is Vuex?” page before you continue. Once you are ready, let’s continue and take a look at how it’s used in myPrayerJournal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the State
&lt;/h2&gt;

&lt;p&gt;The store (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/store/index.js"&gt;mpj:store/index.js&lt;/a&gt;) exports a single new &lt;code&gt;Vuex.Store&lt;/code&gt; instance, with its &lt;code&gt;state&lt;/code&gt; property defining the items it will track, along with initial values for those items. This represents the initial state of the app, and is run whenever the browser is refreshed.&lt;/p&gt;

&lt;p&gt;In looking at our store, there are 4 items that are tracked; two items are related to authentication, and two are related to the journal. As part of authentication (which will get a further exploration in its own post), we store the user’s profile and identity token in local storage; the initial values for those items attempt to access those values. The two journal related items are simply initialized to an empty state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutating the State
&lt;/h2&gt;

&lt;p&gt;There are a few guiding principles for mutations in Vuex. First, they must be defined as part of the &lt;code&gt;mutations&lt;/code&gt; property in the store; outside code cannot simply change one state value to another without going through a mutation. Second, they must be synchronous; mutations must be a fast operation, and must be accomplished in sequence, to prevent race conditions and other inconsistencies. Third, mutations cannot be called directly; mutations are “committed” against the current store. Mutations receive the current state as their first parameter, and can receive as many other parameters as necessary.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Side note: although these functions are called “mutations,” Vuex is actually replacing the state on every call. This enables some really cool time-traveling debugging, as tools can replay states and their transformations.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, what do you do when you need to run an asynchronous process - like, say, calling an API to get the requests for the journal? These processes are called actions, and are defined on the &lt;code&gt;actions&lt;/code&gt; property of the store. Actions receive an object that has the state, but it also has a &lt;code&gt;commit&lt;/code&gt; property that can be used to commit mutations.&lt;/p&gt;

&lt;p&gt;If you look at &lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/store/index.js#L87"&gt;line 87 of store/index.js&lt;/a&gt;, you’ll see the above concepts put into action&lt;sup&gt;1&lt;/sup&gt; as a user’s journal is loaded. This one action can commit up to 4 mutations of state. The first clears out whatever was in the journal before, committing the &lt;code&gt;LOADED_JOURNAL&lt;/code&gt; mutation with an empty object. The second sets the &lt;code&gt;isLoadingJournal&lt;/code&gt; property to &lt;code&gt;true&lt;/code&gt; via the &lt;code&gt;LOADING_JOURNAL&lt;/code&gt; mutation. The third, called if the API call resolves successfully, commits the &lt;code&gt;LOADED_JOURNAL&lt;/code&gt; mutation with the results. The fourth, called whether it works or not, commits &lt;code&gt;LOADING_JOURNAL&lt;/code&gt; again, this time with &lt;code&gt;false&lt;/code&gt; as the parameter.&lt;/p&gt;

&lt;p&gt;A note about the names of our mutations and actions - the Vuex team recommends defining constants for mutations and actions, to ensure that they are defined the same way in both the store, and in the code that’s calling it. This code follows their recommendations, and those are defined in &lt;code&gt;action-types.js&lt;/code&gt; and &lt;code&gt;mutation-types.js&lt;/code&gt; in the &lt;code&gt;store&lt;/code&gt; directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Store
&lt;/h2&gt;

&lt;p&gt;So, we have this nice data store with a finite number of ways it can be mutated, but we have yet to use it. Since we looked at loading the journal, let’s use it as our example (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/components/Journal.vue"&gt;mpj:Journal.vue&lt;/a&gt;). On line 56, we wrap up the computed properties with &lt;code&gt;...mapState&lt;/code&gt;, which exposes data items from the store as properties on the component. Just below that, the &lt;code&gt;created&lt;/code&gt; function calls into the store, exposed as &lt;code&gt;$store&lt;/code&gt; on the component instance, to execute the &lt;code&gt;LOAD_JOURNAL&lt;/code&gt; action.&lt;/p&gt;

&lt;p&gt;The template uses the mapped state properties to control the display. On lines 4 and 5, we display one thing if the &lt;code&gt;isLoadingJournal&lt;/code&gt; property is true, and another (which is really the rest of the template) if it is not. Line 12 uses the &lt;code&gt;journal&lt;/code&gt; property to display a &lt;code&gt;RequestCard&lt;/code&gt; (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/components/request/RequestCard.vue"&gt;mpj:RequestCard.vue&lt;/a&gt;) for each request in the journal.&lt;/p&gt;

&lt;p&gt;I mentioned developer sanity above; and in the &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-front-end-4589"&gt;last post&lt;/a&gt;, under the "Components" heading, I said that the logic that has &lt;code&gt;RequestCard&lt;/code&gt; making the decision on whether it should show, instead of &lt;code&gt;Journal&lt;/code&gt; deciding which ones it should show, would make sense. This is where we put those pieces together. The Vuex store is reactive; when data from it is rendered into the app, Vue will update the rendering if the store changes. So, &lt;code&gt;Journal&lt;/code&gt; simply displays a “hang on” note when the journal is loading, and “all the requests” once it’s loaded. &lt;code&gt;RequestCard&lt;/code&gt; only displays if the request should be displayed. And, the entire “brains” behind this is the thing that starts the entire process, the call to the &lt;code&gt;LOAD_JOURNAL&lt;/code&gt; action. We aren’t moving things around, we’re simply displaying the state of things as they are!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Navigation&lt;/code&gt; (&lt;a href="https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/components/common/Navigation.vue"&gt;mpj:Navigation.vue&lt;/a&gt;) is another component that bases its display off state, and takes advantage of the state’s reactivity. By mapping &lt;code&gt;isAuthenticated&lt;/code&gt;, many of the menu items can be shown or hidden based on whether a user is signed in or not. Through mapping &lt;code&gt;journal&lt;/code&gt; and the computed property &lt;code&gt;hasSnoozed&lt;/code&gt;, the “Snoozed” menu link does not show if there are no snoozed requests; however, the first time a request from the journal is snoozed, this one appears &lt;em&gt;&lt;strong&gt;just because the state changed&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is one of the things that cemented the decision to use Vue for the front end&lt;sup&gt;2&lt;/sup&gt;, and is one of my favorite features of the entire application. &lt;em&gt;(You’ve probably picked up on that already, though.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We’ve now toured our stateful front end; next time, we’ll take a look at &lt;a href="https://dev.to/danieljsummers/a-tour-of-myprayerjournal-the-api-56l5"&gt;the API we use to get data into it&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;Pun not originally intended, but it is now!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; &lt;em&gt;The others were the lack of ceremony and the Single File Component structure; both of those seem quite intuitive.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>vuex</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
