markdown-notes-engine
v2.0.1
Published
A complete markdown note-taking engine with Git-like version control (PostgreSQL) or GitHub integration and media hosting (R2/S3)
Downloads
34
Maintainers
Readme
Markdown Notes Engine
A complete, production-ready markdown note-taking engine with Git-like version control (PostgreSQL) or GitHub integration and media hosting (Cloudflare R2/AWS S3). Add powerful note-taking capabilities to any Node.js application with just a few lines of code.
Features
- Full Markdown Support - Rich markdown editing with live preview
- Git-Like Version Control - Choose between PostgreSQL (recommended) or GitHub for version control
- PostgreSQL: Content-addressable storage with SHA-256 hashing, commit history, and branching
- GitHub: Traditional GitHub-based storage (legacy support)
- Content-Addressable Storage - Deduplication and integrity verification
- Full Commit History - Track every change with parent references and timestamps
- Media Hosting - Image and video uploads to Cloudflare R2 or AWS S3
- Full-Text Search - PostgreSQL tsvector search or GitHub code search
- Syntax Highlighting - Beautiful code blocks with highlight.js
- Auto-save - Never lose your work with automatic saving
- Dark Mode - Built-in dark mode support with persistence
- Responsive - Works beautifully on mobile and desktop
- Keyboard Shortcuts - Boost productivity with keyboard shortcuts
- Drag & Drop - Upload media with drag and drop
- File History - Get complete commit history for any file
Quick Start
Installation
npm install markdown-notes-engineBasic Usage
This package is ESM-only. Use ES module import statements:
import express from 'express';
import { createNotesRouter } from 'markdown-notes-engine';
const app = express();
async function startServer() {
const notesRouter = await createNotesRouter({
database: {
host: 'localhost',
port: 5432,
database: 'markdown_notes',
user: 'postgres',
password: 'your_password',
branch: 'main',
author: 'your_name'
},
storage: {
type: 'r2',
accountId: process.env.R2_ACCOUNT_ID,
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
bucketName: process.env.R2_BUCKET_NAME,
publicUrl: process.env.R2_PUBLIC_URL
}
});
app.use('/api/notes', notesRouter);
app.listen(3000, () => {
console.log('Notes engine running on http://localhost:3000');
});
}
startServer();Version Control Options
Option 1: PostgreSQL (Recommended)
PostgreSQL-backed version control with Git-like architecture:
import { createNotesRouter } from 'markdown-notes-engine';
const notesRouter = await createNotesRouter({
database: {
// Option A: Connection string (Supabase, Heroku, etc.)
connectionString: process.env.DATABASE_URL,
branch: 'main',
author: 'your_name'
// Option B: Individual parameters (local PostgreSQL)
// host: 'localhost',
// port: 5432,
// database: 'markdown_notes',
// user: 'postgres',
// password: 'your_password',
// branch: 'main',
// author: 'your_name'
},
storage: {
type: 'r2', // or 's3'
accountId: process.env.R2_ACCOUNT_ID,
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
bucketName: process.env.R2_BUCKET_NAME,
publicUrl: process.env.R2_PUBLIC_URL
},
options: {
autoInitSchema: true, // Auto-initialize database schema
autoUpdateReadme: true // Auto-update README.md with note list
}
});PostgreSQL Features:
- Content-addressable storage (SHA-256 hashing)
- Full commit history with parent references
- Branches support
- No size limits
- Full-text search with PostgreSQL tsvector
- Automatic schema migrations
- Works with local PostgreSQL, Supabase, Heroku Postgres, etc.
Option 2: GitHub (Legacy)
GitHub-based version control:
import { createNotesRouter } from 'markdown-notes-engine';
const notesRouter = await createNotesRouter({
github: {
token: process.env.GITHUB_TOKEN,
owner: process.env.GITHUB_OWNER,
repo: process.env.GITHUB_REPO,
branch: 'main'
},
storage: {
type: 'r2',
accountId: process.env.R2_ACCOUNT_ID,
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
bucketName: process.env.R2_BUCKET_NAME,
publicUrl: process.env.R2_PUBLIC_URL
}
});Note: GitHub mode requires the optional @octokit/rest dependency:
npm install @octokit/restRemote Database Support
Works with any PostgreSQL database:
Supabase
{
database: {
connectionString: process.env.DATABASE_URL, // Use Connection Pooling URL (port 6543)
branch: 'main',
author: 'your_name'
}
}See docs/SUPABASE.md for detailed Supabase setup instructions.
Heroku Postgres
{
database: {
connectionString: process.env.DATABASE_URL,
ssl: 'require',
branch: 'main',
author: 'your_name'
}
}Local PostgreSQL
{
database: {
host: 'localhost',
port: 5432,
database: 'markdown_notes',
user: 'postgres',
password: 'your_password',
branch: 'main',
author: 'your_name'
}
}Frontend Integration
Using the Bundled Editor
<!DOCTYPE html>
<html>
<head>
<title>My Notes App</title>
<link rel="stylesheet" href="node_modules/markdown-notes-engine/lib/frontend/styles.css">
</head>
<body>
<div id="notes-app"></div>
<script type="module">
import { NotesEditor } from 'markdown-notes-engine/frontend';
const editor = new NotesEditor({
container: '#notes-app',
apiEndpoint: '/api/notes',
theme: 'dark',
autoSave: true,
autoSaveDelay: 2000
});
</script>
</body>
</html>Custom Frontend
Build your own frontend using the API endpoints:
// Get file structure
const structure = await fetch('/api/notes/structure').then(r => r.json());
// Get note content
const note = await fetch('/api/notes/note?path=my-note.md').then(r => r.json());
// Save note
await fetch('/api/notes/note', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: 'my-note.md', content: '# Hello World' })
});
// Delete note (supports both query params and body)
await fetch('/api/notes/note?path=my-note.md', { method: 'DELETE' });
// or
await fetch('/api/notes/note', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: 'my-note.md' })
});
// Get file history (PostgreSQL only)
const history = await fetch('/api/notes/history?path=my-note.md').then(r => r.json());
// Search notes
const results = await fetch('/api/notes/search?q=hello').then(r => r.json());API Reference
Complete API Endpoints
| Method | Endpoint | Description | Params |
|--------|----------|-------------|--------|
| GET | /structure | Get file tree | - |
| GET | /note | Get note content | ?path=file.md |
| POST | /note | Save/update note | { path, content } |
| DELETE | /note | Delete note | ?path=file.md or { path } |
| POST | /folder | Create folder | { path } |
| DELETE | /folder | Delete folder | { path } |
| GET | /history | Get file commit history | ?path=file.md (PostgreSQL only) |
| GET | /search | Full-text search | ?q=query |
| POST | /upload-image | Upload image | FormData with image file |
| POST | /upload-video | Upload video | FormData with video file |
| POST | /upload-image-base64 | Upload base64 image | { image, filename, folder } |
| POST | /render | Render markdown | { markdown } |
Response Formats
Structure Response:
{
"structure": [
{
"name": "folder",
"type": "folder",
"path": "folder",
"children": [
{
"name": "note.md",
"type": "file",
"path": "folder/note.md"
}
]
}
]
}Note Response:
{
"path": "my-note.md",
"content": "# Hello World\n\nThis is my note.",
"sha": "abc123..."
}Save Response:
{
"success": true,
"path": "my-note.md",
"sha": "def456...",
"commit": {
"sha": "commit-sha",
"message": "Update my-note.md"
}
}History Response (PostgreSQL):
[
{
"sha": "commit-id",
"message": "Update note",
"author": "user",
"date": "2025-01-03T12:00:00.000Z",
"blobHash": "content-hash",
"changed": true
}
]Search Response:
{
"results": [
{
"path": "my-note.md",
"content": "# Hello World",
"score": 0.95,
"preview": "...matching text..."
}
]
}Upload Response:
{
"success": true,
"url": "https://your-bucket.r2.dev/images/abc123.jpg",
"path": "images/abc123.jpg",
"filename": "abc123.jpg"
}Configuration Options
Database Configuration
{
database: {
// Connection string (recommended for remote databases)
connectionString: 'postgresql://user:pass@host:5432/db',
// Or individual parameters
host: 'localhost',
port: 5432,
database: 'markdown_notes',
user: 'postgres',
password: 'your_password',
// Connection pool settings
maxConnections: 20,
idleTimeout: 30,
connectionTimeout: 10,
// SSL configuration
ssl: 'prefer', // 'require', 'prefer', or false
// Version control settings
branch: 'main',
author: 'your_name'
}
}Storage Configuration
Cloudflare R2:
{
storage: {
type: 'r2',
accountId: 'your-account-id',
accessKeyId: 'your-access-key-id',
secretAccessKey: 'your-secret-key',
bucketName: 'your-bucket',
publicUrl: 'https://pub-xxxxx.r2.dev'
}
}AWS S3:
{
storage: {
type: 's3',
region: 'us-east-1',
accessKeyId: 'your-access-key-id',
secretAccessKey: 'your-secret-key',
bucketName: 'your-bucket',
publicUrl: 'https://your-bucket.s3.amazonaws.com'
}
}Options
{
options: {
autoInitSchema: true, // Automatically initialize database schema
autoUpdateReadme: true // Auto-update README.md with note list
}
}Database Schema
The PostgreSQL version control uses a Git-like architecture:
- blobs: Content storage (content-addressable by SHA-256)
- commits: Commit records with messages, authors, and timestamps
- trees: File path to blob hash mappings for each commit
- branches: Named pointers to commits (like Git branches)
- media: Uploaded media file references
- search_index: Full-text search index using PostgreSQL tsvector
See lib/backend/db/schema.sql for the complete schema.
Migration Guide
If you have an existing database from an older version, the schema includes automatic migrations. Simply restart your application with autoInitSchema: true and the migrations will run automatically.
See MIGRATION.md for detailed migration instructions.
Examples
Check out the /examples directory for complete working examples:
- postgres-example.js - PostgreSQL version control with full Git-like history
- Express App - GitHub-based version control (legacy)
- .env.example - Environment variable template
Version Control Comparison
| Feature | PostgreSQL | GitHub | |---------|-----------|--------| | Storage Limits | Unlimited | 100GB per repo | | API Dependencies | None | GitHub API required | | Speed | Fast (local or pooled DB) | Network dependent | | Search | PostgreSQL full-text | GitHub code search | | Control | Full ownership | External service | | Cost | Database hosting | Free for public repos | | Version History | Full Git-like history | Native Git history | | File History | ✅ Built-in | ❌ Not available | | Branching | ✅ Supported | ✅ Supported | | Content Deduplication | ✅ SHA-256 hashing | ❌ No |
Architecture
Backend Modules
createNotesRouter(config)- Express router factory (async function)VersionControlClient- PostgreSQL-backed Git-like version controlDatabaseConnection- PostgreSQL connection pool manager (usespostgrespackage)GitHubClient- GitHub API wrapper for note storage (legacy, requires@octokit/rest)StorageClient- R2/S3 client for media uploads (uses AWS SDK)MarkdownRenderer- Markdown to HTML renderer with syntax highlighting
Frontend Component
NotesEditor- Complete note-taking UI component with:- Markdown editor with live preview
- File tree navigation
- Dark mode support
- Keyboard shortcuts
- Drag & drop media uploads
- Auto-save functionality
Project Structure
markdown-notes-engine/
├── lib/ # NPM package source (ESM)
│ ├── backend/ # Express router and API
│ │ ├── db/ # Database schema and connection
│ │ │ ├── connection.js # PostgreSQL connection manager
│ │ │ └── schema.sql # Database schema with migrations
│ │ ├── routes/ # API route handlers
│ │ │ ├── notes.js # Note CRUD operations
│ │ │ ├── search.js # Search functionality
│ │ │ └── upload.js # Media upload handlers
│ │ ├── index.js # Main backend entry point
│ │ ├── github.js # GitHub client (legacy)
│ │ ├── version-control.js # PostgreSQL version control
│ │ ├── storage.js # S3/R2 storage client
│ │ └── markdown.js # Markdown renderer
│ ├── frontend/ # Editor UI component
│ │ ├── index.js # NotesEditor class
│ │ └── styles.css # Styles and dark mode
│ └── index.js # Main package entry point
├── examples/ # Usage examples
│ ├── postgres-example.js # PostgreSQL example
│ ├── .env.example # Environment template
│ └── express-app/ # GitHub integration example
├── docs/ # Documentation
│ └── SUPABASE.md # Supabase setup guide
├── MIGRATION.md # Database migration guide
└── package.json # Package configuration (ESM)Requirements
- Node.js >= 16.0.0
- For PostgreSQL version control (recommended):
- PostgreSQL database (version 12 or higher)
- Works with local PostgreSQL, Supabase, Heroku Postgres, etc.
- For GitHub version control (legacy):
- GitHub personal access token with
reposcope - Install optional dependency:
npm install @octokit/rest
- GitHub personal access token with
- For media hosting:
- Cloudflare R2 or AWS S3 account
Keyboard Shortcuts
Ctrl/Cmd + S- Save noteCtrl/Cmd + P- Toggle preview modeCtrl/Cmd + N- New noteCtrl/Cmd + U- Upload imageCtrl/Cmd + Shift + U- Upload video
Development
Running the Demo
# Install dependencies
npm install
# Copy environment template
cp examples/.env.example .env
# Edit .env with your credentials
# Set up PostgreSQL database or GitHub token
# Configure R2/S3 storage
# Start the development server
npm run devOpen http://localhost:3000 to see the demo application.
Building the Package
The package is already built and ready to use. All source files are in the lib/ directory and use ES modules.
Environment Variables
PostgreSQL Setup
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=markdown_notes
DB_USER=postgres
DB_PASSWORD=your_password_here
# Or use connection string (Supabase, Heroku, etc.)
# DATABASE_URL=postgresql://postgres:password@host:6543/postgres
# Storage Configuration (R2 or S3)
R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_BUCKET_NAME=your_bucket_name
R2_PUBLIC_URL=https://pub-xxxxxx.r2.devGitHub Setup (Legacy)
# GitHub Configuration
GITHUB_TOKEN=ghp_xxxxxxxxxxxx
GITHUB_OWNER=your-username
GITHUB_REPO=your-notes-repo
GITHUB_BRANCH=main
# Storage Configuration
R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_BUCKET_NAME=your_bucket_name
R2_PUBLIC_URL=https://pub-xxxxxx.r2.devTroubleshooting
Database Connection Issues
Error: "connect ETIMEDOUT"
- For Supabase: Use Connection Pooling URL (port 6543), not Direct Connection
- Increase
connectionTimeoutin database config - Check firewall settings
Error: "column does not exist"
- The schema has automatic migrations
- Restart your app with
autoInitSchema: true - See MIGRATION.md for manual migration steps
Error: "UNSAFE_TRANSACTION"
- This is handled automatically by the
postgrespackage - Ensure you're using the latest version
Import/Module Issues
Error: "Cannot use import statement outside a module"
- Add
"type": "module"to your package.json - This package is ESM-only
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see LICENSE for details
Support
Acknowledgments
Built with:
- Express - Web framework
- Marked - Markdown parser
- Highlight.js - Syntax highlighting
- PostgreSQL - Database and version control
- postgres - PostgreSQL client for Node.js
- Octokit - GitHub API client (optional)
- AWS SDK - S3/R2 client
Changelog
v1.0.1
- Migrated to ESM-only package
- Added PostgreSQL version control with Git-like architecture
- Added file history endpoint
- Improved database connection handling with
postgrespackage - Added support for Supabase and other remote PostgreSQL databases
- Added automatic schema migrations
- Flexible DELETE endpoint (supports query params or body)
- Built-in JSON body parser middleware
Made with ❤️ for markdown lovers everywhere
