Decentralized notes on IPFS powered by Pinata. Write, pin, and share via unique CIDs.
- Live demo: https://hashnotes.casaislabs.com
- Overview
- Architecture
- Live Demo
- Screenshot
- Quick Start
- Environment Variables
- Pinata Setup
- Usage
- API
- Security
- Flow
HashNotes is a Next.js App Router application that lets you create short notes and pin them to IPFS using Pinata. Notes are immediately usable via a public gateway URL and can be listed back on the UI.
- Next.js App Router with server and client components.
- Secure server endpoints:
POST /api/notesuploads text content to Pinata (IPFS).GET /api/noteslists previously pinned notes.
- Proxy with strict Content Security Policy in production (
proxy.ts). - Client UI components:
NoteForm,NotesList,NotePanel. - Local cache using
localStoragemerged with server listing after hydration.
- Prerequisites:
- Node.js 18+ and npm
- Pinata account
- Install dependencies:
npm install
- Configure environment:
- Create a
.envfile at the project root with the variables below.
- Create a
- Run locally:
npm run dev
- Build and run production:
npm run buildnpm start
Create hashnotes/.env with:
PINATA_JWT=your_jwt_token
NEXT_PUBLIC_GATEWAY_URL=your-gateway.mypinata.cloud
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NODE_ENV=production
PINATA_JWT: Pinata JWT used server-side to authenticate uploads and listings.NEXT_PUBLIC_GATEWAY_URL: your Pinata gateway subdomain (no protocol).NEXT_PUBLIC_SITE_URL: the public URL of your site (used for metadata).NODE_ENV: set toproductionin deployments to enable hardening.
Never commit secrets. Use your platform’s secret manager.
- Sign up at https://pinata.cloud and verify your account.
- Create a JWT with minimal permissions:
- Resources:
org:files:read,org:files:write - Gateways:
org:gateways:read(optional, for conversions) - Avoid admin-level scopes.
- Resources:
- Note your gateway domain (e.g.,
your-gateway.mypinata.cloud) and put it inNEXT_PUBLIC_GATEWAY_URL. - Paste the JWT into
PINATA_JWTin.env.
Uploads are tagged with keyvalues { app: "hashnotes" } for easy filtering.
- Create a note in the UI and upload to IPFS.
- The note appears immediately with a CID and a gateway link.
- The list shows only HashNotes-tagged items and merges with your local cache after hydration.
POST /api/notes- Body:
multipart/form-datawithfile(text or JSON, ≤1MB) - Returns:
{ cid, url } - Errors:
400missing file,413too large,415unsupported type,429rate limit,500config/error
- Body:
GET /api/notes- Query:
limit(default 20, max 50)pageToken(optional pagination)
- Returns:
{ items: [{ cid, url, createdAt, text }], nextPageToken }
- Query:
- Content Security Policy (CSP) via
proxy.tsin production. - Server-side validation:
- MIME whitelist:
text/plain,application/json - Size limit: 1MB
- MIME whitelist:
- Rate limiting per IP:
POST: 10 requests/minuteGET: 20 requests/minute
- Error messages are clear and user-facing in the UI.
For horizontal scaling, back the rate limit store with Redis.
flowchart TD
A[User] --> B[Client UI]
B -->|Create note| C[POST /api/notes]
C --> D[Pinata Upload]
D --> E[CID]
E --> F[Gateway URL]
B -->|List notes| G[GET /api/notes]
G --> H[Pinata Files List]
H --> I[Items]
I --> B
