A multi-tenant content management system built with Node.js and Express that allows you to manage multiple business websites from a single admin panel. Each business tenant can have its own pages, content, assets, navigation menus, and PDF documents.
- Multi-Tenant Architecture: Support multiple businesses with slug-based routing (
/business-slug) or custom domain mapping - Template-Based Pages: Create pages using HTML templates with dynamic content injection via
data-editattributes - Dynamic Content Management: Edit content directly in the admin panel with support for various field types (text, multiline, HTML, images, links, files)
- Asset Management: Upload and manage images and PDFs per business
- PDF to HTML Conversion: Automatically convert uploaded PDFs to HTML for web viewing
- Navigation Menus: Configure custom navigation menus for each business
- Admin Panel: Web-based admin interface for managing all aspects of each business
- Authentication: Session-based authentication with CSRF protection
- Database Migrations: Built-in migration system for schema management
- Runtime: Node.js
- Framework: Express.js
- Database: PostgreSQL
- Session Storage: PostgreSQL via connect-pg-simple
- Template Engine: EJS (admin panel), Cheerio (page rendering)
- Authentication: bcrypt for password hashing
- File Upload: Multer
- users: Admin user accounts with email and password hash
- businesses: Business/tenant records with slug, optional domain, and JSONB settings
- pages: Page definitions per business with slug, title, template path, and home page flag
- content_entries: Dynamic content storage with field types (text, multiline, image, link, html, json, file)
- assets: Uploaded file metadata (images, PDFs) linked to businesses
- pdf_documents: PDF conversion tracking with generated HTML and conversion status
- menu_items: Navigation menu items per business with support for hierarchical structure
- Businesses have many pages, assets, menu items, and content entries
- Pages belong to a business and have page-specific content
- Content entries can be global (business-wide) or page-specific
- PDF documents are linked to assets for file storage
- Clone the repository:
git clone https://github.com/Benjamin-Ferrin/PaperCMS.git
cd Modular-Backend- Install dependencies:
npm install- Set up environment variables:
type nul > .envEdit .env with your configuration:
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
PORT=3000
NODE_ENV=development
SESSION_SECRET=secret-key
SESSION_MAX_AGE_MS=86400000
UPLOAD_DIR=./public/uploads
MAX_UPLOAD_BYTES=10485760
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your-admin-password- Run database migrations:
node scripts/migrate.js- (Optional) Seed the database with sample data:
node scripts/seed.js- Start the server:
node server.jsThe server will start at http://localhost:3000 with the admin panel at http://localhost:3000/admin
- Login at
/adminusing the admin credentials from your.envfile - Create a new business with a unique slug
- Add pages to the business, specifying HTML templates from the
templates/directory - Edit content for each page (global business content or page-specific content)
- Upload assets (images, PDFs) for use in templates
- Configure navigation menu items
- View the public site at
/:business-slugor/:business-slug/:page-slug
Templates are HTML files with special data-edit attributes that mark editable content regions:
<!DOCTYPE html>
<html>
<head>
<title data-edit="pageTitle">Default Title</title>
</head>
<body>
<nav data-menu="main">
<!-- Menu items injected here -->
</nav>
<h1 data-edit="heroTitle">Welcome</h1>
<img data-edit="heroImage" data-edit-type="image" src="" alt="">
<div data-edit="aboutContent" data-edit-type="html">
Default content
</div>
<a data-edit="contactLink" data-edit-type="link" href="">Contact Us</a>
</body>
</html>Supported field types:
text(default): Plain textmultiline: Text with line breaks converted to<br>image: Image with src and alt attributeslink: Anchor tag with href and texthtml: Raw HTML contentfile: File download link with asset ID
The system resolves tenants in two ways:
- By Slug:
http://localhost:3000/business-slugorhttp://localhost:3000/business-slug/page-slug - By Domain: If a business has a custom domain configured, requests to that domain are automatically routed to that business
Reserved slugs that cannot be used: admin, api, uploads, public, favicon.ico, robots.txt
POST /api/content/update- Update content entries (requires authentication)POST /api/upload- Upload files (requires authentication and CSRF token)
Content is stored as key-value pairs with support for nested structures:
// Global business content
{
businessName: "Al's Diner",
phone: '555-1234',
contact: {
email: { href: 'mailto:hello@example.com', text: 'hello@example.com' },
address: '123 Main St'
}
}
// Page-specific content
{
pageTitle: 'About Us',
aboutContent: { html: '<p>Our story...</p>' }
}Apply pending migrations:
node scripts/migrate.jsRollback last migration:
node scripts/migrate.js downThe seed script creates:
- An admin user (from environment variables)
- A sample business ("Al's Diner")
- Sample pages (home, about)
- Sample content
- Sample menu items
node scripts/seed.jsCreate paired migration files in migrations/:
008_feature_name.up.sql- Schema changes008_feature_name.down.sql- Rollback script
- Passwords are hashed using bcrypt
- Sessions are stored in PostgreSQL with httpOnly cookies
- CSRF protection on all state-changing routes
- File uploads are validated for type and size
- Tenant isolation ensures businesses can only access their own data
MIT License
Copyright (c) 2026
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.