My home-baked bookmark manager
Table of Contents
Context #
A few days ago I stumbled upon the blog of Stephen Ango (the creator of Obsidian, the app I used for all my notes and writing) and found out about his Obsidian Web Clipper, a bookmarklet to save websites as Obsidian notes.
His bookmarklet does the following (source):
- extract the readable content from the page and convert it into Markdown
- create a frontmatter using information found in the page’s
meta
section - create a note in Obsidian using the Obsidian URI protocol
Essentially, an application to locally store bookmarks in ~150 lines of JavaScript code that can be run on any device where a compatible browser and Obsidian have been installed. No cloud, no additional app/code to install: brilliant! …but then my eyes fell on the Troubleshooting section:
This bookmarklet may not work on all websites and browsers. […] The most common error is that a website or the browser itself is blocking third party code execution. This is commonly due to the
connect-src
Content Security Policy (CSP) used by some websites.
At this point I was already so interested in the topic that I decided to roll out my own solution.
bookmarkd #
After a few early-morning programming sessions, I published bookmarkd: it’s a Go application that can run either as a standalone HTTP server or as a Vercel Serverless Function.
It accepts HTTP requests to bookmark a webpage and then performs the following steps:
- fetch the readable content using go-readability, a porting of Mozilla’s readability library
- sanitize its HTML content using bluemonday
- convert its content to Markdown using html-to-markdown
- create a minimal frontmatter
- create a note in Obsidian using the Obsidian URI Protocol
Since I wanted a bookmark manager available on all my devices, having a publicly-accessible backend service was an important requirement for me: I don’t have a private server, so being able to run code as a serverless function was the natural next step in my investigation and I ended up looking at Vercel.
Vercel Serverless Function #
The Vercel Serverless Functions’ runtime for Go makes a developer’s life really easy: any .go
file placed in the /api
folder that defines a func(http.ResponseWriter, *http.Request)
function gets deployed as a serverless function, where the filename becomes the last element of the path.
For example, a handler like the following (the function name is not relevant)
func Handle(w http.ResponseWriter, r *http.Request) {
...
}
placed in the /api/bookmarks.go
file is deployed at https://<project_url>/api/bookmarks
.
The only catch I have found so far is that the function doesn’t redirect me straight to Obsidian: instead, it redirects me to a webpage with a Found
link. If I click it, I’m then prompted to create a note in Obsidian.
Thanks to their choice of relying on standard net/http
handler functions, I was able to reuse the very same code to serve an equivalent endpoint in an HTTP server.
Bonus: the first 100k function invocations are free of charge, and they are… well, more than enough to power my application for several years 🙂
HTTP server #
As I have mentioned, my repository also contains a standalone HTTP server (powered by chi), which exists both as an option to run a private server and to enable the bulk migration of bookmarks without consuming serverless invocations in Vercel (more on that in a follow-up post).
Bookmarklet #
As with the original web clipper, I’m also using a bookmarklet as a trigger: it prompts the user to (optionally) provide some tags, then it sends an HTTP request to the backend service, which takes it over from there.