Narcissus is a proof of concept backend as a service app which lets you create a blog site quicker by managing important blog features like comment and message forms as well as post views and likes. You create the styling and implementation your way on your preferred framework and just use the API to connect the data your site needs. The API runs on Rust Cloudflare Workers interfacing with a Supabase PostgreSQL database and packs features like bot and spam detection. We also use bots for good — a Telegram bot sends you contact form messages! There is a demo frontend built in SvelteKit with the demo site running on Cloudflare Pages at narcissus-blog.rodneylab.com.
These features are provided by REST endpoints which the Rust Cloudflare Worker listens on:
Message form — lets website visitors send a message to admins. To avoid abuse by bots hCaptcha runs a challenge in browser. On the Cloudflare worker side, there is also a check with the Akismet spam detection service. The worker sends you or admins the details of the message using a Telegram bot.
View count — pages views are counted automatically and displayed, letting visitors know what the most popular content is.
Likes — the Cloudflare worker records new blog post likes to a Supabase database.
Comments — comments left by users on blog posts, like contact form messages are checked for spam and bots.
Developer friendly — you style the pages and implement any or all of the features the way you want. Just fetch data from the API using REST calls. Your site becomes backendless and you save on having to configure and connect multiple services.
Newsletter subscription handling, connecting to your preferred service.
GraphQL API.
Alternative demo site built with Astro.
You will need a Cloudflare account as well as an Akismet, hCaptcha and Telegram Bot API key to use all features. These services all have a free tier and there are details below on how you can set them up.
Start by cloning this repo:
git clone https://github.com/rodneylab/narcissus
cd narcissus
Continue by setting up a Cloudflare account if you do not yet have one.
Now get you can get API keys for Akismet, hCaptcha and Telegram. Follow these links for instructions:
If you do not yet have a Rust development environment set up on your machine, head over to the official Rust site for the recommended one-line terminal command to get that up and running.
Install the wrangler tool on your machine:
cargo install wrangler
Next link your Cloudflare account to your local environment:
wrangler login
Now we will define some variables. Start with your Akismet API key:
wrangler secret put AKISMET_API_KEY
paste in your API key when prompted.
Repeat with the following keys:
wrangler secret put HCAPTCHA_SECRETKEY
wrangler secret put HCAPTCHA_SITEKEY
wrangler secret put SUPABASE_URL
wrangler secret put SUPABASE_SERVICE_API_KEY
wrangler secret put TELEGRAM_BOT_API_TOKEN
wrangler secret put TELEGRAM_BOT_CHAT_ID
we also need the url of your user site (e.g. "http://example.com") for Akismet spam detection:
wrangler secret put SITE_URL
Finally we will define the CORS origins. This is a comma separated list of valid domains you want to be able to send requests from (typically your live client site and a local development site). If you have the the following domains:
Enter the secret as https://www.example.com,http://127.0.0.1:3000
when prompted.
Let's define this now then:
wrangler secret put CORS_ORIGIN
Be sure to add appropriate disclaimers and information on privacy policies and terms of use on any sites you link up.
That should be everything set up.
To test you can build the SvelteKit demo site, create you own new site in your framework of preference or update an existing site.
As an example, using fetch
in JavaScript, you can query the data endpoint like so
const response = await fetch(`${process.env["VITE_WORKER_URL"]}/post/data`, {
method: "POST",
credentials: "omit",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
slug,
}),
});
const data = await response.json();
const { comments, likes, views } = data;
Endpoints:
post/data
get the likes, views and comments for a particular blog post:
{ "slug": "example-post-slug-to-identify-post" }
{
"likes": 53,
"views": 598 ,
"comments": [
[
{
"uid": "66df2153-0db5-4c90-a04f-652fbf93098a",
"created_at": "2021-10-20T04:01:07.753+00:00",
"updated_at": "2021-10-20T04:01:07.753+00:00",
"author": "John",
"text": "Hello, just a test comment",
},
{
"uid": "cb090530-9b2a-4a53-a9cf-f974b9fe8bb6",
"created_at": "2021-10-20T04:28:38.973051+00:00",
"updated_at": "2021-10-20T04:28:38.973051+00:00",
"author": "Matthew",
"text": "Another test comment",
}
]
],
}
post/comment
submit a new comment for a particular blog post:
```json
{
"author": "River Costa",
"text": "Comment text",
"email": "[email protected]",
"response": "This is the hCaptcha response received in the client browser on completing the challenge",
"slug": "example-post-slug-to-identify-post"
}
```
</dd>
<dt>response</dt><dd>400 status code if everything is good</dd>
post/like
submit a new like or rescind an existing one for a particular blog post:
```json
{
"id": "id of like being deleted or empty string for a new like",
"unlike": "boolean true is like is being deleted and false otherwise",
"slug": "example-post-slug-to-identify-post"
}
```
</dd>
<dt>response</dt><dd>JSON object:
```json
{
"likes": 53,
"id": "string: id of new like (needed to rescind it later)",
}
```
</dd>
post/message
submit a message, for example from a contact page message form:
```json
{
"name": "River Costa",
"text": "Message text",
"email": "[email protected]",
"response": "This is the hCaptcha response received in the client browser on completing the challenge",
}
```
</dd>
<dt>response</dt><dd>400 status code if everything is good</dd>
post/view
submit a view, for example once the user has scrolled part way down the page of a blog post:
```json
{
"slug": "example-post-slug-to-identify-post"
}
```
</dd>
<dt>response</dt><dd>JSON object:
```json
{
"views": 599,
}
```
</dd>
Supabase data schema - to be added.