<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Barry Frost — Articles</title><description>Articles from Barry Frost</description><link>https://new.barryfrost.com</link><item><title>App defaults in late 2023</title><link>https://new.barryfrost.com/articles/app-defaults-in-late-2023</link><guid isPermaLink="true">https://new.barryfrost.com/articles/app-defaults-in-late-2023</guid><pubDate>Wed, 08 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Following the recent &lt;a href=&quot;https://defaults.rknight.me/&quot;&gt;trend&lt;/a&gt; of posting your default apps, here are mine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📨 Mail Client: Mimestream, Gmail app&lt;/li&gt;
&lt;li&gt;📮 Mail Server: Google Workspace&lt;/li&gt;
&lt;li&gt;📝 Notes: Reflect&lt;/li&gt;
&lt;li&gt;✅ To-Do: Things, Reflect Tasks&lt;/li&gt;
&lt;li&gt;🟦 Photo Management: iCloud Photos&lt;/li&gt;
&lt;li&gt;📆 Calendar: iCloud Calendar&lt;/li&gt;
&lt;li&gt;📁 Cloud File Storage: iCloud Drive&lt;/li&gt;
&lt;li&gt;📖 RSS: Readwise Reader&lt;/li&gt;
&lt;li&gt;🙍🏻‍♂️ Contacts: iCloud Contacts&lt;/li&gt;
&lt;li&gt;🌐 Browser: Safari&lt;/li&gt;
&lt;li&gt;💬 Chat: WhatsApp, Slack&lt;/li&gt;
&lt;li&gt;🔖 Bookmarks: &lt;a href=&quot;https://barryfrost.com/bookmarks&quot;&gt;/bookmarks&lt;/a&gt;, Pinboard&lt;/li&gt;
&lt;li&gt;📑 Read It Later: Readwise Reader&lt;/li&gt;
&lt;li&gt;📜 Word Processing: Google Docs, Pages&lt;/li&gt;
&lt;li&gt;📈 Spreadsheets: Google Sheets&lt;/li&gt;
&lt;li&gt;📊 Presentations: Google Slides, Keynote&lt;/li&gt;
&lt;li&gt;🛒 Shopping Lists: Notes (iCloud sharing)&lt;/li&gt;
&lt;li&gt;📰 News: The Guardian, BBC News&lt;/li&gt;
&lt;li&gt;🎵 Music: Spotify&lt;/li&gt;
&lt;li&gt;🎤 Podcasts: Spotify&lt;/li&gt;
&lt;li&gt;🔐 Password Management: 1Password&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;/via &lt;a href=&quot;https://hacdias.com/2023/11/08/app-defaults-late-2023/&quot;&gt;hacdias&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Creating a tunnel to localhost with Cloudflare</title><link>https://new.barryfrost.com/articles/tunnel</link><guid isPermaLink="true">https://new.barryfrost.com/articles/tunnel</guid><pubDate>Sat, 11 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I found this handy tip via @wesbos. You can create a temporary tunnel to localhost on your machine using &lt;a href=&quot;https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/local/&quot;&gt;Cloudflare&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even better, if you use Cloudflare for a domain’s DNS, you can set up a permanent, named tunnel with a subdomain. Here’s how it works:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ cloudflared tunnel login&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ cloudflared tunnel create local &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ cloudflared tunnel route dns local http://local.yourdomain.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ cloudflared tunnel run --url http://localhost:9999 local&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve previously used &lt;a href=&quot;https://ngrok.com&quot;&gt;ngrok&lt;/a&gt; for free temporary URLs when developing against external APIs, but to create permanent tunnels was a paid feature. This is a nice, free alternative through a service I’m already using.&lt;/p&gt;
</content:encoded></item><item><title>My serverless, headless, Micropub-powered, personal website</title><link>https://new.barryfrost.com/articles/vibrancy</link><guid isPermaLink="true">https://new.barryfrost.com/articles/vibrancy</guid><pubDate>Tue, 13 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; &lt;em&gt;This is my new personal &lt;a href=&quot;https://indieweb.org/&quot;&gt;IndieWeb&lt;/a&gt; website built using serverless AWS services, written in Node.js with the &lt;a href=&quot;https://arc.codes/&quot;&gt;Architect framework&lt;/a&gt;. The backend is a &lt;a href=&quot;https://micropub.net/&quot;&gt;Micropub&lt;/a&gt; server with a separate frontend that fetches posts using Micropub queries, rendering pages behind a CDN.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Look, I know it’s a developer cliché for the majority of posts on your blog to be about rewriting your own personal website software, but I find my website is the perfect place to try out new technology and then document what I’ve learned.&lt;/p&gt;
&lt;p&gt;So, yes, this is a blog post about my new website. As well as a refreshed design, it’s completely different behind the scenes. The project is called &lt;a href=&quot;https://github.com/barryf/vibrancy&quot;&gt;Vibrancy&lt;/a&gt;. It’s massively over-engineered! But that’s the point: learn, have fun and enjoy slowly hacking away after the kids go to bed.&lt;/p&gt;
&lt;h2 id=&quot;goals&quot;&gt;Goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Instant updates&lt;/strong&gt;. My website has over 10K posts and even the fastest static site generators take ~10 seconds to build and deploy so many files. I want the time between hitting create/update and the page refreshing to be instant.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save money&lt;/strong&gt;. My &lt;a href=&quot;https://barryfrost.com/2016/11/colophon&quot;&gt;previous&lt;/a&gt;, low-traffic website cost $16/month on Heroku for a Hobby tier dyno, plus a PostgreSQL database with over 10K rows. I love Heroku, but that’s a bit much for my little website. I wanted my website to cost a few dollars on AWS, after the &lt;a href=&quot;https://aws.amazon.com/free/&quot;&gt;always free&lt;/a&gt; limits.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use new (to me) technology:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Serverless&lt;/strong&gt;: My website doesn’t get much traffic and having an always-on server seemed wasteful. It seemed like a good use-case for trying small, stateless Node.js functions that can be called on-demand without reserving compute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS&lt;/strong&gt;: While I’d used EC2 and S3 before, I wanted to experiment with other AWS services like Lambda, API Gateway, DynamoDB, SNS and CloudWatch that complement a serverless approach.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;: Ruby is a comfortable pair of shoes. I used it for previous versions of this website and it felt like time for a change. Node.js is a well-supported choice for Serverless.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;framework&quot;&gt;Framework&lt;/h2&gt;
&lt;p&gt;I’m using &lt;a href=&quot;https://arc.codes/&quot;&gt;Architect&lt;/a&gt; for both the backend and frontend apps. I spent some time prototyping with the &lt;a href=&quot;https://www.serverless.com/&quot;&gt;Serverless Framework&lt;/a&gt;, but was left frustrated at its incomplete support for local development. This is where Architect &lt;a href=&quot;https://arc.codes/docs/en/guides/get-started/why-architect&quot;&gt;shines&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Architect is an opinionated developer experience (DX) for building database backed web apps with AWS. We remove all the noise and friction to building serverlessly. We prioritize speed with fast local dev, smart configurable defaults and flexible Infrastructure as Code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At its core is the &lt;a href=&quot;https://arc.codes/docs/en/guides/get-started/project-layout&quot;&gt;app.arc manifest file&lt;/a&gt; and a file structure based around primitives for HTTP requests, events, queues, scheduled tasks, tables, static files and more. Each maps to an AWS service, for example &lt;code&gt;tables&lt;/code&gt; corresponds to DynamoDB tables and &lt;code&gt;queues&lt;/code&gt; to SQS. Architect then provides helpers to simplify working with each service. And by running &lt;code&gt;arc deploy&lt;/code&gt;, it builds a SAM application that is magically deployed via CloudFormation to AWS.&lt;/p&gt;
&lt;p&gt;Architect has been a breath of fresh air and reminds me of how natural Rails felt the first time I tried it. It deserves a proper article.&lt;/p&gt;
&lt;h2 id=&quot;backend&quot;&gt;Backend&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Backend architecture&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1820&quot; height=&quot;1060&quot; src=&quot;/_astro/vibrancy-backend.CbHr-2WS_Pmu9w.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Vibrancy’s backend architecture, hosted using AWS.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;headless-cms&quot;&gt;Headless CMS&lt;/h3&gt;
&lt;p&gt;I like the concept of separating the management of my content from its display by adopting a &lt;a href=&quot;https://en.wikipedia.org/wiki/Headless_content_management_system&quot;&gt;headless CMS&lt;/a&gt; architecture. In theory, I‘m free to build multiple frontends that all use the same backend. There are plenty of excellent headless CMS options, but I built my own. Why? Well, I wanted to embrace the constraints of managing content exclusively via &lt;a href=&quot;https://micropub.net/&quot;&gt;Micropub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By implementing the Micropub server specification, Vibrancy has a mature, well-documented API for creating, updating, deleting, reading and querying posts. There is no admin system. Instead, using a Micropub client like &lt;a href=&quot;https://micropublish.net/&quot;&gt;Micropublish&lt;/a&gt; or &lt;a href=&quot;https://quill.p3k.io/&quot;&gt;Quill&lt;/a&gt;, I can log in (via &lt;a href=&quot;https://www.w3.org/TR/indieauth/&quot;&gt;IndieAuth&lt;/a&gt;) and manage my content.&lt;/p&gt;
&lt;h3 id=&quot;media-endpoint&quot;&gt;Media endpoint&lt;/h3&gt;
&lt;p&gt;Vibrancy offers a &lt;a href=&quot;https://indieweb.org/micropub_media_endpoint&quot;&gt;Micropub media endpoint&lt;/a&gt;: a method to upload images and get a URL for use in posts. &lt;a href=&quot;https://cloudinary.com&quot;&gt;Cloudinary&lt;/a&gt; is used to upload, store and serve photos. Its ability to dynamically-resize images means I can upload one large file and then request different sizes on the fly, keeping frontend page sizes down. Images are then also uploaded to GitHub.&lt;/p&gt;
&lt;h3 id=&quot;background-events&quot;&gt;Background events&lt;/h3&gt;
&lt;p&gt;Using Architect’s &lt;code&gt;event&lt;/code&gt; handlers, the server fires off asynchronous tasks in the background to avoid blocking page requests. Lambda functions are handled using SNS.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Syndication&lt;/strong&gt;: if specified when I create a post, the backend will &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt; notes to Twitter and bookmarks to &lt;a href=&quot;http://pinboard.in/&quot;&gt;Pinboard&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webmentions&lt;/strong&gt;: when a post is created or updated, the server will send &lt;a href=&quot;https://webmentions.net/&quot;&gt;webmentions&lt;/a&gt; to any links using &lt;a href=&quot;https://telegraph.p3k.io&quot;&gt;Telegraph&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backup&lt;/strong&gt;: all posts, webmentions and photos are also stored in a private git repo on GitHub.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contexts&lt;/strong&gt;: to display a snippet of the source of any bookmarks, RSVPs, replies, reposts or likes, the server uses &lt;a href=&quot;http://granary.io/&quot;&gt;Granary&lt;/a&gt; to fetch structured data in Microformats format and falls back to Open Graph metadata.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Push&lt;/strong&gt;: the server uses &lt;a href=&quot;https://pushover.net&quot;&gt;Pushover&lt;/a&gt; to send me a push notification whenever a webmention is received.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;storage&quot;&gt;Storage&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt;: Content is indexed and stored in AWS’s DynamoDB. It’s a fast, low-latency database and its &lt;code&gt;tables&lt;/code&gt; are a core primitive in Architect. I’m reasonably happy with it, but found the need to create extra tables a bit dirty when trying to combine filters with pagination. It was worth embracing its differences and learn its constraints to follow the Architect happy path.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: Content is also stored in a git repository on GitHub as a backup. If needed, I could regenerate the DynamoDB database from the repo. I’ve chosen to make the repo private because it’s possible some posts may be private or start off as drafts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudinary&lt;/strong&gt;: As described above, photos are stored in Cloudinary and served from its CDN.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;frontend&quot;&gt;Frontend&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Frontend architecture&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;1364&quot; height=&quot;564&quot; src=&quot;/_astro/vibrancy-frontend.CPee-gJY_Z1IDCR5.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Vibrancy’s frontend architecture, hosted using AWS and Cloudflare.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;micropub-queries&quot;&gt;Micropub queries&lt;/h3&gt;
&lt;p&gt;The frontend website doesn’t have a database or any content files of its own. Instead, when a post is requested, a Micropub &lt;code&gt;source&lt;/code&gt; query is made to the backend and the post is returned in &lt;a href=&quot;http://microformats.org/wiki/microformats2-json&quot;&gt;Microformats 2 JSON&lt;/a&gt; format.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;// Request for https://barryfrost.com/2021/07/a-post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;GET https:&lt;/span&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;//api.barryfrost.com/micropub?q=source&amp;#x26;url=https://barryfrost.com/2021/07/a-post&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;h-entry&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;properties&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;published&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &quot;2021-07-01T12:34:56Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;    &quot;content&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;      &quot;This is my post. I use *Markdown* to mark up text.&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frontend takes this JSON object and renders a page using &lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; templates. It also converts Markdown to HTML if needed.&lt;/p&gt;
&lt;p&gt;Lists of posts are also fetched in the same way, but with additional parameters to filter results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;before&lt;/code&gt; takes an integer representing the epoch time of a post’s published timestamp. This method is used to paginate results.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;limit&lt;/code&gt; is the number of posts to return in the response, defaulted to 20.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like the backend, the frontend uses the Architect framework.&lt;/p&gt;
&lt;h3 id=&quot;style&quot;&gt;Style&lt;/h3&gt;
&lt;p&gt;This is the first project on which I’ve used &lt;a href=&quot;https://tailwindcss.com&quot;&gt;Tailwind CSS&lt;/a&gt;. Initially it felt like &lt;a href=&quot;https://barryfrost.com/2020/11/i-ve-been-using-tailwindcss-for&quot;&gt;heresy&lt;/a&gt;, but I quickly began to enjoy how fast it was to build solid, responsive layouts without needing to bounce back-and-forth between HTML and CSS files. Tailwind also provides a built-in dark mode with very little configuration.&lt;/p&gt;
&lt;p&gt;I’m using inline SVGs for icons instead of an icon font to help further decrease page load times.&lt;/p&gt;
&lt;h3 id=&quot;cdn&quot;&gt;CDN&lt;/h3&gt;
&lt;p&gt;The frontend is not static. It queries and renders pages on demand. However, posts are cached and served using Cloudflare’s CDN (Content Delivery Network). I use very long &lt;code&gt;s-maxage&lt;/code&gt; cache headers for posts which mean that requests are less likely to hit the frontend. If a post is updated Vibrancy sends a flush API request to Cloudflare.&lt;/p&gt;
&lt;h2 id=&quot;webmentions&quot;&gt;Webmentions&lt;/h2&gt;
&lt;p&gt;Vibrancy fully supports &lt;a href=&quot;https://webmentions.net/&quot;&gt;webmentions&lt;/a&gt; for replies, reposts, likes or mentions from another IndieWeb website or service.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Receiving&lt;/strong&gt;: Vibrancy currently uses &lt;a href=&quot;http://webmention.io&quot;&gt;webmention.io&lt;/a&gt; to receive webmentions, accepting a Microformats payload using a webhook. Next on my list is &lt;a href=&quot;/articles/building-a-webmention-receiver&quot;&gt;building my own receiver&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sending&lt;/strong&gt;: Webmentions are sent via &lt;a href=&quot;https://telegraph.p3k.io&quot;&gt;Telegraph&lt;/a&gt; using a background event whenever a post is created or updated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backfeed&lt;/strong&gt;: Using the magic of &lt;a href=&quot;https://brid.gy/&quot;&gt;Bridgy&lt;/a&gt;, responses to my syndicated copies (e.g. tweets) are pulled back as webmentions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;open-source&quot;&gt;Open source&lt;/h2&gt;
&lt;p&gt;Vibrancy’s source code is available on GitHub for both the &lt;a href=&quot;https://github.com/barryf/vibrancy&quot;&gt;backend&lt;/a&gt; and &lt;a href=&quot;https://github.com/barryf/barryfrost&quot;&gt;frontend&lt;/a&gt; using the &lt;a href=&quot;https://opensource.org/licenses/MIT&quot;&gt;MIT licence&lt;/a&gt;. Feel free to poke around, but I’m still actively developing and improving it and I wouldn’t recommend using Vibrancy for your own site just yet.&lt;/p&gt;
</content:encoded></item><item><title>Building a webmention receiver</title><link>https://new.barryfrost.com/articles/building-a-webmention-receiver</link><guid isPermaLink="true">https://new.barryfrost.com/articles/building-a-webmention-receiver</guid><pubDate>Sat, 15 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In today’s &lt;a href=&quot;https://events.indieweb.org/2021/05/webmentions-beyond-webmention-io-zG4JpHhZShVA&quot;&gt;Webmentions Beyond Webmention.io&lt;/a&gt; online meet-up there was a request for people who were willing to implement their own receiver.&lt;/p&gt;
&lt;p&gt;I’m interested. I’d like to build an endpoint that replaces what I currently use webmention.io for: receiving, storing and sending a webhook to my server with the parsed webmention.&lt;/p&gt;
&lt;p&gt;It wouldn’t be a drop-in replacement. I’m also not sure I’m ready to host and run a service for others right now. But I think a receiver would be a useful building block and I’m willing to open-source it for self-hosting.&lt;/p&gt;
&lt;p&gt;Let’s see how I get on.&lt;/p&gt;
</content:encoded></item><item><title>Micropublish: IndieAuth updates and supported properties feature</title><link>https://new.barryfrost.com/articles/micropublish-indieauth</link><guid isPermaLink="true">https://new.barryfrost.com/articles/micropublish-indieauth</guid><pubDate>Mon, 14 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yesterday I pushed a new release of &lt;a href=&quot;https://micropublish.net&quot;&gt;Micropublish&lt;/a&gt; to include recent updates for clients to the &lt;a href=&quot;https://indieauth.spec.indieweb.org/&quot;&gt;IndieAuth specification&lt;/a&gt;, as summarised in &lt;a href=&quot;https://aaronparecki.com&quot;&gt;Aaron Parecki&lt;/a&gt;’s &lt;a href=&quot;https://aaronparecki.com/2020/12/03/1/indieauth-2020&quot;&gt;IndieAuth 2020 write-up&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The two biggest IndieAuth changes are the use of a OAuth 2.0 PKCE (Proof Key for Code Exchange) to secure authorisation and confirming the auth server if the original &lt;code&gt;me&lt;/code&gt; value differs from that returned from the token endpoint. See the &lt;a href=&quot;https://github.com/barryf/micropublish/issues/54&quot;&gt;GitHub issue&lt;/a&gt; for more.&lt;/p&gt;
&lt;p&gt;Additionally, Micropublish now has experimental support for &lt;a href=&quot;https://github.com/barryf/micropublish/issues/51&quot;&gt;queries for supported properties, for a supported post-type&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://www.jvt.me&quot;&gt;Jamie Tanna&lt;/a&gt; and &lt;a href=&quot;https://david.shanske.com/&quot;&gt;David Shanske&lt;/a&gt; for their help debugging and testing.&lt;/p&gt;
</content:encoded></item><item><title>Micropublish v2.3.0</title><link>https://new.barryfrost.com/articles/micropublish-2-3-0</link><guid isPermaLink="true">https://new.barryfrost.com/articles/micropublish-2-3-0</guid><pubDate>Mon, 12 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve pushed a biggish update to &lt;a href=&quot;https://micropublish.net&quot;&gt;Micropublish&lt;/a&gt; to bring it to &lt;a href=&quot;https://github.com/barryf/micropublish/releases/tag/v2.3.0&quot;&gt;v2.3.0&lt;/a&gt;. It includes some of the stable and experimental &lt;a href=&quot;https://indieweb.org/Micropub-extensions&quot;&gt;Micropub extensions&lt;/a&gt; discussed in the IndieWeb &lt;a href=&quot;https://indieweb.org/2020/Pop-ups/Micropub&quot;&gt;pop-up session&lt;/a&gt; I joined earlier this year.&lt;/p&gt;
&lt;h3 id=&quot;added&quot;&gt;Added&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/barryf/micropublish/issues/45&quot;&gt;Filter syndication targets by post-type, specify checked as appropriate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/barryf/micropublish/issues/42&quot;&gt;Raw content instead of HTML for Articles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/barryf/micropublish/issues/36&quot;&gt;Support &lt;code&gt;visibility&lt;/code&gt; property&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/barryf/micropublish/issues/35&quot;&gt;Support &lt;code&gt;post-status&lt;/code&gt; property&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/barryf/micropublish/issues/33&quot;&gt;Add granular scopes to login/auth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;changed&quot;&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/barryf/micropublish/issues/41&quot;&gt;Make JSON the default post creation method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/barryf/micropublish/issues/39&quot;&gt;Bump kramdown from 2.1.0 to 2.3.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Only show edit, delete or undelete controls if scope allows&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;draft&lt;/code&gt; scope to login form&lt;/li&gt;
&lt;li&gt;Force &lt;code&gt;post-status&lt;/code&gt; to &lt;code&gt;draft&lt;/code&gt; when using (only) draft scope&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Leaving Venntro</title><link>https://new.barryfrost.com/articles/leaving-venntro</link><guid isPermaLink="true">https://new.barryfrost.com/articles/leaving-venntro</guid><pubDate>Tue, 03 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After ten happy, successful and highly educational years, I’ve decided to leave my role as &lt;a href=&quot;https://www.linkedin.com/in/barryfrost&quot;&gt;Chief Technology Officer&lt;/a&gt; at &lt;a href=&quot;https://venntro.com&quot;&gt;Venntro&lt;/a&gt; this month. After so long with the company I felt the need to shake myself out of my comfort zone and look for a fresh challenge.&lt;/p&gt;
&lt;p&gt;I’m very excited about joining a new company where I can use my experiences creating high performance development teams, building a large B2C subscription platform and leading technology for an award-winning and profitable fast-growth business.&lt;/p&gt;
&lt;p&gt;If you’re looking for a CTO / VP of Engineering / Head of Development etc. in South-West London, North Surrey or along the M4 Corridor (or know of someone who is) I’d love to &lt;a href=&quot;/contact&quot;&gt;talk&lt;/a&gt;. I’m planning to first enjoy some time at home with our &lt;a href=&quot;/2018/02/hello-to-my-son-charlie&quot;&gt;newborn&lt;/a&gt;, but I’ll be open to new opportunities from the end of May.&lt;/p&gt;
&lt;p&gt;And of course, I wish founders Ross and Steve and the whole of Venntro continued success for the future. I’m confident the platform is in very good hands with Ian as Venntro’s new Head of Technology leading a great team of superb developers.&lt;/p&gt;
</content:encoded></item><item><title>Making a looping lullaby box</title><link>https://new.barryfrost.com/articles/lullaby</link><guid isPermaLink="true">https://new.barryfrost.com/articles/lullaby</guid><pubDate>Fri, 29 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My wife and I are expecting our second baby in the new year. Our first, now a toddler, loved his &lt;a href=&quot;https://www.sweetdreamers.co.uk/product/ewan-the-dream-sheep/&quot;&gt;Ewan The Dream Sheep&lt;/a&gt; at night, especially its gentle harp lullaby music, but the short duration (20 minutes) meant we were frequently restarting the music to help him fully drift off.&lt;/p&gt;
&lt;p&gt;I wanted a better solution this time around with a song that lasts for hours. All the off-the-shelf baby products I found were similarly limited. I looked through several cheap Bluetooth speakers, but I didn’t want to have to leave my phone connected all day/night long. The &lt;a href=&quot;https://www.anker.com/uk/products/variant/SoundCore-Mini-Bluetooth-Speaker/A3101111&quot;&gt;Anker SoundCore Mini&lt;/a&gt;, however, also accepts a microSD card so can be left to play independently. I ordered one from Amazon for a very reasonable £16.99 and a SanDisk 16Gb microSD card (with SD adapter to go in my Mac) for £6.50.&lt;/p&gt;
&lt;p&gt;Next I needed a lullaby track lasting for at least 12 hours, thinking optimistically that the baby may sleep that long. Ignoring all the spammy-looking “free download MP3 baby lullaby” links on Google, I decided to make my own.&lt;/p&gt;
&lt;p&gt;I first paid for and downloaded a gentle piano recording of &lt;a href=&quot;https://itunes.apple.com/gb/album/brahms-lullaby/1065946325?i=1065946793&quot;&gt;Brahm’s Lullaby from iTunes&lt;/a&gt; on my Mac. I converted the M4A/AAC file to an MP3 in iTunes (File &amp;gt; Convert &amp;gt; Create MP3 Version) – the SoundCore doesn’t play AAC files – and then copied the MP3 file to my desktop as &lt;code&gt;lullaby.mp3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This created a single song lasting 150 seconds, but I needed this to loop. So I installed &lt;a href=&quot;http://ffmpeg.org&quot;&gt;ffmpeg&lt;/a&gt; to process and convert the audio. I specified the LAME MP3 library to create a final MP3 file.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ brew install ffmpeg --with-libmp3lame&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The ~150s track played 300 times would result in approximately 12.5 hours of music. The following command specifies 300 loops, takes an input of the original lullaby and uses the LAME library at the same 190kbit/s quality of the original (&lt;code&gt;qscale:a 2&lt;/code&gt;).&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;$ ffmpeg -stream_loop 300 -i lullaby.mp3 -codec:a libmp3lame -qscale:a 2 lullaby-loop.mp3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a few minutes of processing I had a large (~1Gb) file ready to transfer via the SD card adapter to the microSD card and the SoundCore speaker. One extra I needed to add was a USB-A charger to power the speaker, but I had one spare lying around so that wasn’t an issue. It all works beautifully.&lt;/p&gt;
&lt;p&gt;Now let’s see if this all pays off and the little monkey sleeps when they arrive!&lt;/p&gt;
</content:encoded></item><item><title>How we make remote working work</title><link>https://new.barryfrost.com/articles/how-we-make-remote-working-work</link><guid isPermaLink="true">https://new.barryfrost.com/articles/how-we-make-remote-working-work</guid><pubDate>Wed, 18 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;Our remote team&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;680&quot; height=&quot;113&quot; src=&quot;/_astro/remoters.BMrRlGDu_2rXHtz.webp&quot; srcset=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now that, according to a recent study, &lt;a href=&quot;https://www.owllabs.com/state-of-remote-work&quot;&gt;over half of employees work remotely at
least once per week&lt;/a&gt;, remote working is no longer considered an exception.
Companies that recognise the benefits and work hard to support effective remote
working are rewarded with happy and productive people.&lt;/p&gt;
&lt;p&gt;This post describes how we’ve structured our team and processes in the
&lt;a href=&quot;http://dev.venntro.com&quot;&gt;development team&lt;/a&gt; at &lt;a href=&quot;https://www.venntro.com&quot;&gt;Venntro&lt;/a&gt;. It follows a chat with our remote
team (naturally, over &lt;a href=&quot;https://slack.com&quot;&gt;Slack&lt;/a&gt;) about their experiences working remotely with
us, and I’ve included their comments.&lt;/p&gt;
&lt;h2 id=&quot;the-100-remote&quot;&gt;The 100% remote&lt;/h2&gt;
&lt;p&gt;We have six people in our development team who are 100% remote: four spread
around the UK and two in New Zealand. Of the six, all but one began working with
us in one of our offices first and then switched to remote later on. This early
face-time with colleagues helped build strong relationships and absorb knowledge
before switching to full-time remote.&lt;/p&gt;
&lt;h2 id=&quot;the-occasionally-remote&quot;&gt;The occasionally remote&lt;/h2&gt;
&lt;p&gt;It’s not just the fully-remote who work outside the office. Any of the regular
office team can also opt to work from home. When someone is expecting a
delivery, has a medical appointment or needs to take their car to be serviced,
it’s fine to work remotely.&lt;/p&gt;
&lt;p&gt;We also encourage anyone to work from home if they’re starting to feel a bit
poorly and not well enough to travel, but not too sick to work. Not only does
this help with recovery, it prevents the spread of early-stage germs to avoid
others being infected.&lt;/p&gt;
&lt;h2 id=&quot;how-we-make-it-work&quot;&gt;How we make it work&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chat&lt;/strong&gt;: Like many other development teams, we make heavy use of &lt;a href=&quot;https://slack.com&quot;&gt;Slack&lt;/a&gt;. We
chat in &lt;code&gt;chatter&lt;/code&gt;, discuss work in team channels and share things we’ve found in
channels like &lt;code&gt;newbie&lt;/code&gt; and &lt;code&gt;ux&lt;/code&gt;. It’s the glue that binds everyone together,
whether co-located or remote.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video&lt;/strong&gt;: Almost as vital is Google &lt;a href=&quot;https://hangouts.google.com/&quot;&gt;Hangouts&lt;/a&gt; for video conversations. We
use a form of Scrum to organise our teams: every stand-up, sprint planning
session and retrospective involves a Hangout. The integration with Slack means
anyone can kick off an ad-hoc video conversation from within a channel, alerting
and bringing together the whole team. We sometimes also use &lt;a href=&quot;https://screenhero.com&quot;&gt;Screenhero&lt;/a&gt; for
screen sharing and collaboration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Web tools&lt;/strong&gt;: Just like our company’s &lt;a href=&quot;http://www.whitelabeldating.com&quot;&gt;product&lt;/a&gt;, the tools we use are
web-based, so can be accessed from anywhere. Source code is hosted on GitHub
and we use its &lt;a href=&quot;https://help.github.com/articles/about-pull-requests/&quot;&gt;Pull Requests&lt;/a&gt; to collaborate on features and code-review.
Stories are created and prioritised in &lt;a href=&quot;https://www.atlassian.com/software/jira&quot;&gt;Jira&lt;/a&gt;, mock-ups are reviewed in
&lt;a href=&quot;https://www.invisionapp.com&quot;&gt;InVision&lt;/a&gt; and our documentation written-up in &lt;a href=&quot;https://www.atlassian.com/software/confluence&quot;&gt;Confluence&lt;/a&gt;. Google’s
&lt;a href=&quot;https://gsuite.google.com&quot;&gt;GSuite&lt;/a&gt; handles our company email, calendars and office documents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Equipment&lt;/strong&gt;: Almost everyone in the team uses a MacBook Pro with plenty of
memory plugged in to one or more big displays. And this includes remote workers.
We also provide external keyboards, trackpads, mice and any other peripherals to
make their environment comfortable and productive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Management&lt;/strong&gt;: Line managers hold scheduled weekly 1:1s and regular appraisals
with remote people over Hangouts, in addition to ad-hoc daily chats over Slack.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team&lt;/strong&gt;: For each and every team event we get together via a Hangout. Each
Monday I run a 10-minute meeting updating the whole team on what’s happening.
When someone sadly leaves the team we gather for presents and goodbyes. Even our
Secret Santa presents are sent in advance and then opened synchronously with
remotes!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Company&lt;/strong&gt;: And don’t forget company events and celebrations. For anything
involving the wider company we set up a wide-angle camera and broadcast over a
Hangout. It’s just as important to include remote people to hear from the CEO
and other senior management.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Face-to-face&lt;/strong&gt;: Each month we invite the whole team (including remoters) to
our head office in Windsor for our regular company meeting. There is often a
charitable or social element to the day, e.g. a pub quiz or sports day, and so
the team can mix with colleagues they wouldn’t generally interact. We also
organise team workshops or tech talks on the same day which are
broadcast/recorded for those unable to attend.&lt;/p&gt;
&lt;h2 id=&quot;remote-benefits-our-people&quot;&gt;Remote benefits our people&lt;/h2&gt;
&lt;p&gt;A common theme in feedback from our remote people was about their better
work-life balance, especially no longer requiring long commutes which “drained”
them and meant their only downtime was at weekends.&lt;/p&gt;
&lt;p&gt;While some people enjoy open-plan offices, others find working at home more
productive: “I don’t miss loud phone calls and blaring radios from other
departments” and “Fewer distractions and better focus for me.”&lt;/p&gt;
&lt;p&gt;Being remote hasn’t damaged career prospects. Three of our team progressed to
senior positions after going fully remote. Appraisals are still conducted and
promotions awarded regardless of location.&lt;/p&gt;
&lt;h2 id=&quot;remote-benefits-our-company&quot;&gt;Remote benefits our company&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.owllabs.com/state-of-remote-work&quot;&gt;Companies that support remote work have 25% lower employee turnover than
companies that don’t&lt;/a&gt;. We’ve retained several of our most experienced
people through embracing remote. By insisting individuals travel to our offices
I don’t believe that would have been possible.&lt;/p&gt;
&lt;p&gt;We see fewer employee sick days: as one of our remoters commented, “My health
has improved since going remote. Much less exposure to the communal germ pool.”&lt;/p&gt;
&lt;p&gt;While it doesn’t exactly qualify as “&lt;a href=&quot;https://en.wikipedia.org/wiki/Follow-the-sun&quot;&gt;following the sun&lt;/a&gt;”, our two NZ
developers are online and available earlier in the day to help react to
emergencies or catch up with overnight issues.&lt;/p&gt;
&lt;h2 id=&quot;challenges-to-overcome&quot;&gt;Challenges to overcome&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Staying in the loop&lt;/strong&gt;: Where possible, we try to kick off ad-hoc Hangouts to
discuss issues with everyone in the team instead of an in-office conversation.
We don’t always get it right when fire-fighting and this can be a frustration
for those offsite.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Being ignored&lt;/strong&gt;: Our remote people say it can also be frustrating to ping
someone on Slack when it’s important and not get a response. “You can’t just go
over and kick someone!” We encourage team-based public channels rather than
private channels or direct messages so that others can also jump in to help.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Culture&lt;/strong&gt;: With remote people, it’s important to work even harder on company
and team culture. This can be tough, but by reinforcing the attitude to always
consider remoters’ needs in everything you do, the feeling of remoteness can be
reduced.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trust&lt;/strong&gt;: It’s clearly essential that people that work remotely are trusted to
do so by their team and manager. Our approach has been to start off with a trial
period working at home to allow both sides to feel comfortable and establish
expectations early on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pros and cons&lt;/strong&gt;: Inevitably some things can’t be replicated through tools or
process: “I miss pub lunches and the pool table. Oh, and the coffee machine,”
but, “I find the convenience of working from home makes up for the loss of
in-office benefits.”&lt;/p&gt;
&lt;h2 id=&quot;tips-and-advice&quot;&gt;Tips and advice&lt;/h2&gt;
&lt;p&gt;When chatting to our remote people I finished by asking them for their own tips
and advice for making a success of working remotely. Here are their suggestions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Work out a routine ahead of time and stick to it. Don’t work near a TV. And
you need a good chair. Also remember to take a lunch break away from the
computer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s important to have a quiet space free from distractions. Especially if
you have children. And having the right equipment helps too (e.g. big
monitor).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I think getting out of the house before or after work or at lunch time for
some exercise is important.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I tend to take small breaks from the screen to grab a drink and talk to the
guinea pigs. So maybe all remoters should have guinea pigs.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Using JSON in Postgres with Ruby and Sequel</title><link>https://new.barryfrost.com/articles/postgres-json-sequel</link><guid isPermaLink="true">https://new.barryfrost.com/articles/postgres-json-sequel</guid><pubDate>Tue, 10 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With its fast, built-in support for JSON, it’s worth considering Postgres for storing and querying your JSON data. Instead of using a specialised document server, Postgres may be the right choice for your application.&lt;/p&gt;
&lt;p&gt;As of &lt;a href=&quot;https://www.postgresql.org/docs/9.4/static/release-9-4.html&quot;&gt;Postgres 9.4&lt;/a&gt; you can index data stored in a JSONB (binary JSON) column using GIN (“Generalised Inverted Index”) indexes. JSONB/GIN provides special operators to efficiently and rapidly query data.&lt;/p&gt;
&lt;p&gt;And for Ruby developers, the &lt;a href=&quot;http://sequel.jeremyevans.net&quot;&gt;Sequel&lt;/a&gt; gem offers a collection of convenient methods that make it easy to query Postgres JSONB columns.&lt;/p&gt;
&lt;h2 id=&quot;example-usage&quot;&gt;Example usage&lt;/h2&gt;
&lt;p&gt;First, install the &lt;code&gt;sequel_pg&lt;/code&gt; gem:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sh&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;$&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; gem&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; sequel_pg&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a table with a JSONB column and a GIN index and add some sample JSON data:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Create a table with a JSONB column&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; TABLE&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (permalink &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VARCHAR&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;255&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;PRIMARY KEY&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; JSONB);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Add an index using GIN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; INDEX&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; posts_gin&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; ON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;USING&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; GIN(&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;-- Insert a row with a JSON document&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;INSERT INTO&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; posts &lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;VALUES&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/2017/10/my-post&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;title&quot;: &quot;My post&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;content&quot;: &quot;This is a new post that I have created.&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;category&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;ruby&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;        &quot;postgres&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &quot;published&quot;: &quot;2017-10-10T16:05:10Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;}&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then in Ruby:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;rb&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Require the Sequel gem&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; &apos;sequel&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Include the Postgres JSON Operations extension&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;Sequel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:pg_json_ops&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Connect to your Postgres instance via your database URL&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Sequel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;connect&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Ask Sequel to use the Postgres JSON extension with your database&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;extension&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:pg_json&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Create a JSONB Operation object for the &quot;data&quot; jsonb column in our table&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FFAB70&quot;&gt;data&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt; =&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt; Sequel&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;pg_jsonb_op&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:data&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Find posts containing title &quot;My post&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get_text&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;title&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) =&gt; &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;My post&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Find posts where the category array has a &quot;postgres&quot; value&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;category&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;contains&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;([&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;postgres&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;]))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Find posts where the first value in the category array is &quot;ruby&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;category&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;get_text&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;) =&gt; &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;ruby&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Find posts which have a content key&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;where&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;has_key?&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;content&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A737D&quot;&gt;# Find posts sorted by the date published&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;DB&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;:posts&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;].&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;order&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(data[&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;published&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read the &lt;a href=&quot;http://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/pg_json_ops_rb.html&quot;&gt;Sequel documentation&lt;/a&gt; for further methods supported by the &lt;code&gt;pg_json_ops&lt;/code&gt; extension.&lt;/p&gt;
&lt;h2 id=&quot;the-right-choice&quot;&gt;The right choice?&lt;/h2&gt;
&lt;p&gt;Postgres is a great solution for JSON in your application, especially if you are already using Postgres for structured data in other tables. It offers easy storage and, with GIN indexes and the Sequel gem, you get fast querying of data from Ruby.&lt;/p&gt;
&lt;p&gt;Where it may not be the right choice is if your use-case requires frequent partial updates to JSON documents. You can of course retrieve the JSON blob, parse it, update the value in the hash, convert back to JSON and then update the column, but a dedicated document database like MongoDB may be a better option.&lt;/p&gt;
</content:encoded></item></channel></rss>