For the longest time I wanted to be a writer. I loved reading as a kid, and I wanted to give that gift back to "the next generation" or whatever.
To satisfy this dream, I started writing fan fiction (I won't link to it, but if you find it, congratulations), and it has been a joy. The site I have been posting to offers some analytics, but it is nothing deeper than a hit counter.
Jump to heading The plan
Using Deno Deploy and JSONBin.io, I'm going to hand roll an analytics system that can be added to the content by using an <img>
tag.
<img src="https://deno-deploy-subdomain.deno.com/?param1=a¶m2=b" alt="" aria-hidden="true" height="1" width="1" />
First thing to do is open up a Deno Deploy playground and get some requests received. The code will look like this:
Deno.serve((req: Request) => {
const params = new URL(req.url).searchParams;
const workName = params.get('work');
const chapter = params.get('chapter');
// update the records
return new Response("");
});
Each work will have a name, and they will have chapters. Each work will look roughly like this inside of JSONBin.
{
"workname": [
{
"chapter": 1,
"views": [{ ts: Date.now(), formatted: /* formatted version using Intl.DateTimeFormat */ }],
"viewCount": 1
}
]
}
Next let's get JSONBin.io set up and add those requests.
Deno.serve(async (req: Request) => {
// ...
const bin = await fetch('https://api.jsonbin.io/v3/b/$BIN_ID/latest', {
headers: new Headers({ 'X-Master-Key': Deno.env.get('key') })
});
let { record } = await bin.json();
if (workName in record === false) {
/* Create a record inside for the viewed work */
}
for (const chapterRecord of record[workName]) {
chapterRecord.views.push({ ts, formatted });
chapterRecord.viewCount += 1;
}
});
Finally, I send up the updated record to JSONBin, which we can do by just doing a fetch using the PUT method:
Deno.serve(async (req: Request) => {
// ...
await fetch('https://api.jsonbin.io/v3/b/$BIN_ID', {
headers: new Headers({
'Content-Type': 'application/json',
'X-Master-Key': Deno.env.get('key')
}),
body: JSON.stringify(record),
method: 'PUT'
})
});
At the bottom of every work I publish now I add a <img>
tag with the required params:
<img src="https://deno-deploy-subdomain.deno.com/?work=MY_WORK_NAME&chapter=1"
alt=""
aria-hidden="true"
width="1"
height="1"
/>
All images need an alt=""
attribute, but since this image is not technically an image, I want to make sure screen readers don't see it either. This is why I've added the aria-hidden="true"
.
Jump to heading And with that, it is done!
Now the analytics are set up. Viewing a work gives a ping to the JSONBin api, adding a view with a timestamp and incrementing the overall count. This isn't a kitchen sink solution and it is in dire need of a frontend for better data visualization.