In today's digital landscape, tracking article views is essential for understanding audience engagement and optimizing content strategies. In this blog post, we'll walk through how to create a scalable and efficient article view counter using the Hono web framework and Supabase as our backend service. By the end of this tutorial, you'll have a robust API endpoint that not only tracks views but also ensures data integrity by creating articles on the fly if they don't exist.
Table of Contents
- Introduction to Hono and Supabase
- Setting Up Your Project
- Configuring Supabase
- Creating the Hono Application
- Implementing the View Counter Logic
- Testing the API Endpoint
- Conclusion
Introduction to Hono and Supabase
What is Hono?
Hono is a lightweight, high-performance web framework for building serverless applications and APIs in TypeScript or JavaScript. It’s designed to be minimalistic yet powerful, making it an excellent choice for developers looking to create fast and efficient web services.
What is Supabase?
Supabase is an open-source Firebase alternative that offers a suite of backend services, including a PostgreSQL database, authentication, storage, and real-time subscriptions. It provides a seamless developer experience with easy integration into various frontend and backend technologies.
By combining Hono and Supabase, we can build a performant API that leverages Supabase's robust backend capabilities.
Setting Up Your Project
Before diving into the code, ensure you have the following prerequisites:
- Node.js installed on your machine.
- A Supabase account. If you don't have one, you can sign up for free.
- TypeScript knowledge is beneficial but not mandatory.
Initialize Your Project
-
Create a new directory for your project and navigate into it:
mkdir hono-supabase-view-counter cd hono-supabase-view-counter
-
Initialize a new Node.js project:
npm init -y
-
Install the necessary dependencies:
npm install hono @supabase/supabase-js npm install -D typescript ts-node @types/node
-
Initialize TypeScript:
npx tsc --init
Configuring Supabase
Creating a Supabase Project
- Log in to your Supabase account and create a new project.
- Note down the Supabase URL and Anon Key from your project's settings. These credentials are essential for connecting your application to Supabase.
Setting Up the Database
We'll create a table named articles
to store article data, including the view counts.
-
Navigate to the SQL editor in your Supabase dashboard.
-
Run the following SQL script to create the
articles
table:create table articles ( id integer primary key, views integer not null default 0 ); -- Create a stored procedure to increment views create or replace function increment_view(row_id integer) returns integer as $$ begin update articles set views = views + 1 where id = row_id; return (select views from articles where id = row_id); end; $$ language plpgsql;
This script creates the articles
table with id
and views
columns and a stored procedure increment_view
to handle view count increments atomically.
Creating the Hono Application
Let's dive into the core of our application. We'll set up the Hono server, connect to Supabase, and define the necessary routes.
Importing Dependencies
import { Hono } from "hono";
import { createClient } from "@supabase/supabase-js";
- Hono: The web framework for handling HTTP requests.
- Supabase Client: For interacting with the Supabase backend.
Initializing Hono
const app = new Hono();
This initializes a new Hono application instance.
Configuring Supabase
const supabaseUrl: string = "https://kayaaylwgggy.supabase.co";
const supabaseAnonKey: string =
"eyJhbGciOiJIUzI14JKET2ZMyqaXffKmllyBfIFY";
const supabase = createClient(supabaseUrl, supabaseAnonKey);
Replace the supabaseUrl
and supabaseAnonKey
with your project's credentials. These keys allow your application to communicate securely with Supabase.
Implementing the View Counter Logic
The core functionality involves checking if an article exists, creating it if it doesn't, and then incrementing its view count.
The checkAndIncreaseViews
Function
async function checkAndIncreaseViews(articleId: number): Promise<number> {
// Check if the article exists
let { data: article, error } = await supabase
.from("articles")
.select("id, views")
.eq("id", articleId)
.single();
if (error && error.code !== "PGRST116") {
throw new Error("Error checking article existence: " + error.message);
}
if (!article) {
// Article doesn't exist, create it with default views = 0
const { data: newArticle, error: insertError } = await supabase
.from("articles")
.insert({ id: articleId, views: 0 })
.single();
if (insertError) {
throw new Error("Error creating article: " + insertError.message);
}
article = newArticle;
}
// Increment the view count using the stored procedure
const { data, error: incrementError } = await supabase.rpc("increment_view", {
row_id: articleId,
});
if (incrementError) {
throw new Error("Error incrementing views: " + incrementError.message);
}
return data as number;
}
Breakdown:
-
Check Article Existence:
- Queries the
articles
table for the givenarticleId
. - If an error occurs that's not related to the article not existing (
PGRST116
), it throws an error.
- Queries the
-
Create Article if Not Found:
- If the article doesn't exist, it inserts a new record with
views
initialized to0
. - Throws an error if the insertion fails.
- If the article doesn't exist, it inserts a new record with
-
Increment View Count:
- Calls the
increment_view
stored procedure to atomically increase the view count. - Returns the updated view count.
- Calls the
Defining the API Route
app.get("/article/view/:id", async (c) => {
const articleId: number = parseInt(c.req.param("id"), 10);
try {
const newViews = await checkAndIncreaseViews(articleId);
return c.json({
message: "Views incremented successfully",
views: newViews,
});
} catch (error) {
return c.json({ error: (error as Error).message }, 400);
}
});
- Route:
GET /article/view/:id
- Functionality:
- Extracts the
id
parameter from the URL. - Calls
checkAndIncreaseViews
to handle the logic. - Returns a JSON response with the updated view count or an error message.
- Extracts the
Exporting the Application
export default app;
This allows the Hono application to be used by other modules or serverless functions.
Testing the API Endpoint
To ensure everything works as expected, let's test the API.
Running the Application
-
Add a start script in your
package.json
:"scripts": { "start": "ts-node index.ts" }
-
Create an
index.ts
file with the Hono application code. -
Start the server:
npm start
-
Test the endpoint using
curl
or any API testing tool like Postman.curl http://localhost:3000/article/view/1
Expected Response:
{ "message": "Views incremented successfully", "views": 1 }
Each subsequent request will increment the
views
count accordingly.
Conclusion
In this tutorial, we've successfully built a robust API endpoint for tracking and incrementing article views using Hono and Supabase. This setup ensures scalability, data integrity, and ease of maintenance. By leveraging Supabase's powerful backend services and Hono's lightweight framework, developers can create efficient and performant applications with minimal overhead.
Future Enhancements
- Authentication: Integrate Supabase Auth to restrict access to certain endpoints.
- Rate Limiting: Prevent abuse by limiting the number of requests per IP or user.
- Caching: Implement caching strategies to reduce database load for frequently accessed articles.
- Analytics: Extend the functionality to track more metrics like unique views, user demographics, etc.
Feel free to customize and expand upon this foundation to suit your specific project needs. Happy coding!