Like Like Like

What do you like?

Dear reader, what do you like? Do you like blog posts? Well do I have one for you. In the past hour I built a like button that can integrate into my blog template then enable a user to add one like to a DB (removing is a future me issue). If that’s something that sounds interesting to you or any of the following hooks, read on!

  • Deno KV Database
  • Fresh API
  • Personal Pride
  • Displayed Engagement

Getting Started

I had a general idea of what I wanted to do:

  • I wanted a way to keep track of number likes for a specific blog post
    • I’m not super worried about tracking user like state at this time, just capturing the value
  • I wanted a way to display that value and allow the user to add to that value
  • I wanted this to integrate easily with my current blog setup (Deno, Fresh, Deno Deploy)

Based on this criteria I abstractly planned:

  • A DB to store the blogs like value
  • An API endpoint
    • GET to read what the blog's likes currently
    • PUT to add one to the like count
  • A React component to display this value and manage the interaction

The intention of this blog is to run through those three functionalities and from a high level how I tackled them. Call me out if I get too in the weeds. Let’s start.

DB

I needed some database that would enable me to easily spin up some way to store a number and continually add one to the number (I'm not letting anyone delete their like - if you give it, it's mine!).

Immediately my thoughts went to things I've heard of but never used. I remembered that Deno has a key value (KV) database built right into it which felt like a clean fit with my blog's stack. In addition I was able to easily integrate it with my hosting platform Deno Deploy. Bish Bash Bosh.

The DB would contain keys based on some id per blog post (I'm currently using the slug that you see in the URL for that ID) and then have an object as the value {id: string, count: number}. Id being duplicated in the key and in the value is probably something worth cleaning up in the long run as the count is all that truly matters but it didn't feel important at the time.

API Endpoint

The API endpoint would allow the react component to integrate with the DB. I'm not the best at API architecture but wanted to start somewhere.

Step 1 - Read what the value is

By creating a GET endpoint I could view what was in the DB via rest calls at any point. It allowed a way for me to monitor the values (as I'm still not totally sure of a GUI for Deno KV) which felt important if I were to make this all work.

I start by getting what the id of the blog post likes you want to read, then check that object and return the whole thing. Using Fresh that amounted to this:

async GET(_req, ctx) {
   const id = ctx.params.id;
   const key = ["likes", id];
   const value = (await kv.get<LikeDB>(key)).value!;
   return new Response(JSON.stringify(value));
},

Looking at it now, it feels good that I was able to get this all configured relatively easily. That said - it is just a basic Read call...

Step 2 - Update what that value is

Ideally there would be a simple path that I could update the value anytime someone hit a button, it would add one to the value that's already there. You may be thinking "not very extensible" and I'd have to agree with you... but past me was moving quickly and wanted results. He came up with this PUT* to solve

async PUT(_req, ctx) {
   const id = ctx.params.id;
   const likeKey = ["likes", id];
   const likeResponse = await kv.get<LikeDB>(likeKey);
   const newResponse = {
       id: id,
       count: likeResponse === null || !likeResponse!.value ? 0 : likeResponse!.value!.count+1
   }
   const ok = await kv.atomic().check(likeResponse).set(likeKey, newResponse).commit();
   if (!ok) throw new Error("Something went wrong.");
   return new Response(JSON.stringify(newResponse));
},

Running this down, you see the first three likes are almost verbatim the get - read id, make key, look at value. The difference here is that I additionally:

  • create a new response
  • validate there is a value in the DB
    • If not set it to value to 0
    • otherwise increment by 1
  • Set the value (I don't totally follow what atomic and check do but they were in the docs.)
  • then return the new response that was saved if someone wants to use it

* PUT is probably not ideal and I want to use PATCH. Last time I used PUT or PATCH was in bootcamp so I'm going to just shrug for now but if someone has a reason I really need to change it - HMU

React Component

I used my local ollama (local LLM) to spin up a basic react component for me which would basically toggle a boolean on when clicked. Then I added the total count to be displayed and had a little heart icon that filled when the user clicked it for some dopamine.

It's simple and probably needs some cleanup but the markup looks like this:

<button onClick={handleClick} type="button" className="flex items-center align-middle">
   <div className="text-sky-200">
       <Heart fill={isLiked}/>
   </div>
   {total}
</button>
  • handleClick runs outbound to the API to run the PUT
  • Total displays the amount
    • It GET the value when the component is mounted
    • It PUT the value there when it updates (that sounded funny in my head but written looks silly)
  • Toggle the fill state on the heart SVG based on if the user liked it or not
  • Toggle isLiked state for the component

All this amounted to the little heart button that you see at the bottom of your screen.

Wrapping it up

I'm honestly pleased that I was able to set up a like button so smoothly. I think its not going to work at scale but this is my personal garden - who cares. If I was to keep this going, I'd want to:

  • Add some animation feedback on the heart button to show users they clicked it
  • Enable a way to persist a users like state across reload
  • Make it so if a user un-likes then re-likes - it doesn't add a new value but instead removes one then adds one.

Surprisingly, I didn't hit many hurdles. The biggest being that denoKV is unstable and newish so I'm really pushing the envelope with understanding. The docs were kind of lacking in that regard so I may need to either write something as a kv primer for people or give some updates to the kv docs.

This was a really fun little project and I think it leaves the blog in a better place than before I started! If you made it this far drop a like! Thanks for reading