Skip to content

Latest commit

Β 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Ink & Fold ✍️

A full-stack, production-ready blogging platform β€” write, publish, and discover thoughtful articles. Built with a serverless-first architecture powered by Hono on Cloudflare Workers and a React + Vite frontend.


✨ Features

For Readers

  • Browse and read all published articles
  • Like and bookmark posts
  • Leave comments on posts

For Writers

  • Rich markdown editor to write and preview articles
  • Pick a cover image directly from Unsplash
  • Submit posts for admin review before publishing
  • Manage your own posts (drafts, under review, published)
  • Delete your posts

Authentication & Security

  • Email + OTP signup verification (via Resend)
  • Access tokens (JWT) + Refresh token rotation (stored in httpOnly cookies)
  • Sign out from current session or all sessions

Admin Panel

  • Review queue for submitted posts (approve / reject with a reason)
  • Ban / unban users (instantly enforced at the edge via Cloudflare KV)
  • Promote users to admin
  • View all registered users
  • AI-powered content moderation using Groq to flag potentially harmful content before human review; automated emails notify authors on flag / approval / rejection

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Client                            β”‚
β”‚            React 19 + Vite + Tailwind CSS v4             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚ HTTPS
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Cloudflare Workers (Edge)                    β”‚
β”‚           Hono framework Β· TypeScript Β· workerd           β”‚
β”‚                                                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Auth Routes   β”‚   β”‚      Blog / Admin Routes     β”‚  β”‚
β”‚  β”‚  JWT + Cookies  β”‚   β”‚   CRUD Β· Likes Β· Bookmarks   β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚           β”‚                           β”‚                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚               Cloudflare KV                         β”‚  β”‚
β”‚  β”‚   INK_FOLD_BANNED_USERS (edge ban enforcement)      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚                 β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚  Prisma Accel.  β”‚  β”‚  Upstash Redis   β”‚
          β”‚  (conn pooling) β”‚  β”‚  (rate limiting) β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
          β”‚   PostgreSQL    β”‚
          β”‚ (Prisma Postgresβ”‚
          β”‚  / Neon / etc.) β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ› οΈ Tech Stack

Layer Technology
Frontend React 19, TypeScript, Vite, Tailwind CSS v4
Backend Hono, TypeScript, Cloudflare Workers
Database PostgreSQL via Prisma ORM
DB Connection Pooling Prisma Accelerate (edge-compatible)
Caching / Rate Limiting Upstash Redis
Edge KV Store Cloudflare KV
AI Moderation Groq SDK
Email Resend
Cover Images Unsplash API
Validation Zod
Auth JWT (Hono/jwt) + httpOnly cookie refresh tokens

πŸ“ Project Structure

ink-and-fold/
β”œβ”€β”€ backend/                   # Hono API β€” Cloudflare Worker
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ index.ts           # All routes and middleware
β”‚   β”‚   └── utils/
β”‚   β”‚       β”œβ”€β”€ auth.ts        # OTP generation & hashing
β”‚   β”‚       β”œβ”€β”€ validator.ts   # Zod schemas
β”‚   β”‚       β”œβ”€β”€ moderator.ts   # Groq content moderation
β”‚   β”‚       └── mailTemplate.ts# Resend email templates
β”‚   β”œβ”€β”€ prisma/
β”‚   β”‚   β”œβ”€β”€ schema.prisma      # Database schema
β”‚   β”‚   └── migrations/        # SQL migration files
β”‚   β”œβ”€β”€ wrangler.jsonc         # Cloudflare Worker config (safe to commit)
β”‚   β”œβ”€β”€ prisma.config.ts       # Prisma config for local migrations
β”‚   β”œβ”€β”€ .dev.vars              # ⚠️ Local secrets β€” gitignored, never commit
β”‚   └── package.json
β”‚
└── frontend/                  # React + Vite SPA
    β”œβ”€β”€ src/
    β”‚   β”œβ”€β”€ App.tsx            # Router and route guards
    β”‚   β”œβ”€β”€ api.ts             # Axios instance + interceptors
    β”‚   β”œβ”€β”€ types.ts           # Shared TypeScript interfaces
    β”‚   └── components/        # All page/UI components
    β”‚       β”œβ”€β”€ Landing.tsx
    β”‚       β”œβ”€β”€ Signup.tsx / Signin.tsx / VerifyOtp.tsx
    β”‚       β”œβ”€β”€ Blogs.tsx      # Feed
    β”‚       β”œβ”€β”€ BlogDetail.tsx # Single post view
    β”‚       β”œβ”€β”€ Write.tsx      # Markdown editor
    β”‚       β”œβ”€β”€ Edit.tsx
    β”‚       β”œβ”€β”€ MyPosts.tsx
    β”‚       β”œβ”€β”€ Admin.tsx      # Admin dashboard
    β”‚       └── UnsplashPicker.tsx
    └── package.json

πŸš€ Getting Started (Local Development)

Prerequisites


Backend Setup

1. Install dependencies

cd backend
pnpm install

2. Configure local secrets

Create a .dev.vars file in the backend/ directory:

DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=YOUR_PRISMA_ACCELERATE_KEY"
JWT_SECRET="your-strong-jwt-secret"
REFRESH_JWT_SECRET="your-strong-refresh-jwt-secret"
UNSPLASH_ACCESS_KEY="your-unsplash-access-key"
RESEND_API_KEY="your-resend-api-key"
GROQ_API_KEY="your-groq-api-key"
UPSTASH_REDIS_REST_URL="https://your-upstash-url.upstash.io"
UPSTASH_REDIS_REST_TOKEN="your-upstash-token"

Note on DATABASE_URL: Two URLs are involved:

  • Direct URL (e.g. postgres://...) β†’ used only for running prisma migrate. Set this in backend/.env.
  • Accelerate URL (e.g. prisma+postgres://accelerate.prisma-data.net/...) β†’ used by the Worker at runtime. Set this in backend/.dev.vars.

3. Apply database migrations

Set your direct database URL in backend/.env, then run:

npx prisma migrate deploy

4. Start the development server

pnpm dev

The backend runs at http://localhost:8787.


Frontend Setup

1. Install dependencies

cd frontend
pnpm install

2. Configure the API URL

Create a .env.local file in the frontend/ directory:

VITE_API_URL=http://localhost:8787

3. Start the dev server

pnpm dev

The frontend runs at http://localhost:5173.


🌐 Deployment

Backend β†’ Cloudflare Workers

1. Authenticate with Cloudflare

npx wrangler login

2. Deploy with secrets uploaded in one command

From the backend directory:

npx wrangler deploy --minify --secrets-file .dev.vars

This deploys the Worker and uploads all secrets from .dev.vars to Cloudflare in one step. Your live URL will be printed on success (e.g. https://backend.<your-subdomain>.workers.dev).


Frontend β†’ Cloudflare Pages / Vercel / Netlify

1. Set the VITE_API_URL environment variable to your deployed Worker URL in your hosting provider's dashboard.

2. Build the production bundle:

cd frontend
pnpm build

3. Deploy the frontend/dist/ folder to your hosting provider.


πŸ“‘ API Reference

All endpoints are prefixed with /api/v1. Protected routes require an Authorization: Bearer <token> header.

Auth

Method Endpoint Auth Description
POST /signup βœ— Register a new user
POST /verify-otp βœ— Verify email with OTP
POST /signin βœ— Sign in, returns access + refresh token
POST /refresh βœ— Refresh access token via cookie
POST /signout βœ“ Sign out current session
POST /signout-all βœ“ Revoke all sessions

Blog

Method Endpoint Auth Description
POST /blog βœ“ Create a new post (triggers AI moderation)
GET /blog/all βœ“ Get all published posts
GET /blog/user βœ“ Get current user's posts
GET /blog/bookmarks βœ“ Get bookmarked posts
GET /blog/:id βœ“ Get a single post by ID
PUT /blog/:id βœ“ Update a post
DELETE /blog/:id βœ“ Delete a post
POST /blog/:id/like βœ“ Toggle like on a post
POST /blog/:id/bookmark βœ“ Toggle bookmark on a post
POST /blog/:id/comment βœ“ Add a comment
GET /blog/:id/comments βœ“ Get all comments for a post
DELETE /blog/comment/:commentId βœ“ Delete a comment

Utilities

Method Endpoint Auth Description
GET /unsplash/search βœ“ Search Unsplash for cover images

Admin (requires ADMIN role)

Method Endpoint Description
GET /admin/review-queue List posts pending review
POST /admin/blog/:id/approve Approve a post for publishing
POST /admin/blog/:id/reject Reject a post with a reason
GET /admin/userslist Get all registered users
POST /admin/promote/:userId Promote a user to admin
POST /admin/users/:userId/ban Ban a user
POST /admin/users/:userId/unban Unban a user

πŸ” Environment Variables Reference

Variable Used By Description
DATABASE_URL Worker (runtime) Prisma Accelerate connection URL
JWT_SECRET Worker Secret for signing access tokens
REFRESH_JWT_SECRET Worker Secret for signing refresh tokens
UNSPLASH_ACCESS_KEY Worker Unsplash API key for image search
RESEND_API_KEY Worker Resend API key for transactional emails
GROQ_API_KEY Worker Groq API key for AI content moderation
UPSTASH_REDIS_REST_URL Worker Upstash Redis REST endpoint
UPSTASH_REDIS_REST_TOKEN Worker Upstash Redis auth token
VITE_API_URL Frontend (build) Backend base URL for the React app

⚠️ Never commit .dev.vars or .env to version control. Both are already listed in .gitignore. The wrangler.jsonc (including KV namespace IDs) is safe to commit β€” those IDs are resource identifiers, not secrets.


🀝 Contributing

Contributions, issues and feature requests are welcome!

  1. Fork the repository
  2. Create a new branch: git checkout -b feat/your-feature
  3. Commit your changes: git commit -m "feat: add your feature"
  4. Push to your branch: git push origin feat/your-feature
  5. Open a Pull Request

For new database models, update prisma/schema.prisma and create a migration:

npx prisma migrate dev --name your_migration_name

πŸ“„ License

MIT Β© Rituraj

About

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages