mango-cms v0.1.15
Mango CMS Documentation
Mango is a powerful, code-first CMS built on MongoDB that emphasizes configuration through code. This documentation covers how to configure and use Mango CMS through the config
folder.
Table of Contents
Collections
Collections are the foundation of Mango CMS. Each collection represents a MongoDB collection and is defined in the config/collections
directory.
Basic Collection Structure
export default {
// Required: Singular form of collection name
singular: 'project',
// Optional: Enable real-time updates via WebSocket
subscribe: true,
// Define collection fields
fields: {
title: String,
description: String,
// ... more fields
},
}
Collection Options
singular
: Required. The singular form of your collection name (e.g., 'project' for projects)subscribe
: Optional. Enable WebSocket subscriptions for real-time updatesformatRequest
: Optional. Custom request formatting before processing
Request Formatting
Use formatRequest
to modify incoming requests before they're processed:
export default {
singular: 'project',
formatRequest(request) {
// Example 1: Add default search criteria
if (request.method === 'read') {
request.search = {
...request.search,
status: 'active'
}
}
// Example 2: Convert create to update for duplicate unique fields
if (request.method === 'create' && request.document.address) {
const existing = await readEntry({
collection: 'project',
search: { address: request.document.address }
})
if (existing) {
request.method = 'update'
request.document.id = existing.id
}
}
return request
}
}
Fields
Mango provides several built-in field types and allows for custom field creation.
Built-in Fields
import fields from '@cms/1. build/fields'
const { Relationship, Timestamp, File, Image } = fields
export default {
fields: {
// Basic Types
title: String,
count: 'Int',
isActive: Boolean,
// Complex Types
createdAt: Timestamp(),
thumbnail: Image(),
attachment: File(),
author: Relationship({
collection: 'user',
single: true,
}),
tags: Relationship({
collection: 'tag',
}),
},
}
Field Options
Relationship Fields
Relationship({
collection: 'user', // Required: Target collection
single: true, // Optional: Single relationship (default: false)
required: true, // Optional: Field is required
default: null, // Optional: Default value
})
Computed Fields
{
fields: {
fullName: {
type: String,
computed: (document) => {
return `${document.firstName} ${document.lastName}`
},
// Optional: Cache expiration in seconds
expiresCache: 3600,
// Optional: Values to provide to the computed function
provide: ['firstName', 'lastName']
}
}
}
Field Translation
Fields can transform data on input and output using translateInput
and translateOutput
:
{
fields: {
price: {
inputType: String, // Input comes as string
type: 'Int', // Stored as integer
// Transform input before saving
translateInput: (value, { request, index, parentValue }) => {
// Convert string price to number and handle currency
return parseFloat(value.replace('$', '')) * 100
},
// Transform output before sending to client
translateOutput: (value, { request, document }) => {
// Convert stored cents to dollars with currency format
return `$${(value / 100).toFixed(2)}`
}
}
}
}
Custom Fields
Create custom fields in config/fields/
directory:
// config/fields/currency.js
export default {
type: 'Currency',
inputType: String,
translateInput(value, { request }) {
return parseFloat(value.replace('$', '')) * 100
},
translateOutput(value, { request, document }) {
return `$${(value / 100).toFixed(2)}`
},
validate(value, { request }) {
if (typeof value !== 'number' || isNaN(value)) {
throw new Error('Invalid currency value')
}
},
}
Using custom fields in collections:
import Currency from '../fields/currency'
export default {
singular: 'product',
fields: {
name: String,
price: Currency(),
salePrice: Currency(),
},
}
Permissions
Mango provides flexible permission control at the collection level.
Basic Permissions
export default {
permissions: {
// Define permissions for each role in member.roles
public: ['create'],
owner: ['read', 'update', 'delete'],
},
}
Function-based Permissions
export default {
async permissions(request) {
// For create requests, always check member permissions
if (!request.id) {
return {
authorized: request.member?.roles.includes('editor'),
}
}
// For existing documents, check ownership or team access
const document = request.originalDocument
return {
authorized:
// Member is the owner
document.createdBy === request.member?.id ||
// Member is part of the document's team
document.team?.includes(request.member?.id) ||
// Member has admin role
request.member?.roles.includes('admin'),
}
},
}
Hooks
Hooks allow you to execute code at different stages of the CRUD operations.
export default {
hooks: {
created: async ({ request, response, document }) => {
// Handle post-creation tasks
console.log('New document created:', document.id)
},
read: async ({ request, response, document }) => {
// Handle during read operation
console.log('Document accessed:', document.id)
},
updated: async ({ request, response, document, originalDocument }) => {
// Handle post-update tasks with access to previous version
console.log('Document updated from:', originalDocument, 'to:', document)
},
deleted: async ({ request, response, document, originalDocument }) => {
// Handle post-deletion tasks with access to deleted document
console.log('Document deleted:', document.id)
console.log('Original state:', originalDocument)
},
},
}
Endpoints
Create custom endpoints by exporting an object structure in config/endpoints/index.js
:
import { sendEmail } from '../services/email'
export default {
contact: {
// POST /endpoints/contact
async post(req) {
const { name, email, message } = req.body
// Validate required fields
if (!name || !email || !message) {
return {
success: false,
error: 'Missing required fields',
}
}
// Send email
await sendEmail({
to: 'support@example.com',
subject: `Contact Form: ${name}`,
text: `From: ${name} (${email})\n\n${message}`,
})
return {
success: true,
message: 'Thank you for your message',
}
},
support: {
// POST /endpoints/contact/support
async post(req) {
const { ticketId, message } = req.body
// Create support ticket
const ticket = await createSupportTicket({
id: ticketId,
message,
member: req.member.id,
})
return {
success: true,
ticket,
}
},
},
},
}
Plugins
Plugins extend Mango's functionality through a structured folder in config/plugins
:
config/plugins/
my-plugin/
collections/
widget.js
gadget.js
fields/
customField.js
endpoints/
index.js
Example plugin structure:
// config/plugins/my-plugin/collections/widget.js
export default {
singular: 'widget',
fields: {
name: String,
configuration: {...}
}
}
// config/plugins/my-plugin/fields/customField.js
export default {
type: 'CustomField',
// field implementation
}
// config/plugins/my-plugin/endpoints/index.js
export default {
widgets: {
async get(req) {
return { success: true }
}
}
}
User Configuration
Extend the built-in member collection by creating config/config/users.js
:
import fields from '@cms/1. build/fields'
const { Relationship, Image } = fields
export default {
// Extend the member collection with custom fields
fields: {
// Built-in fields: email, password, roles
// Add custom fields
displayName: String,
avatar: Image(),
timezone: {
type: String,
default: 'UTC',
},
teams: Relationship({
collection: 'team',
}),
preferences: {
fields: {
theme: String,
notifications: Boolean,
},
default: {
theme: 'light',
notifications: true,
},
},
},
// Add member-specific hooks
hooks: {
created: async ({ document }) => {
// Send welcome email
await sendWelcomeEmail(document.email)
},
updated: async ({ document, originalDocument }) => {
// Handle email change verification
if (document.email !== originalDocument.email) {
await sendEmailVerification(document.email)
}
},
},
// Custom member permissions
permissions: {
owner: ['read', 'update'],
admin: ['create', 'read', 'update', 'delete'],
},
}
The user configuration allows you to:
- Add custom fields to the member collection
- Define member-specific hooks
- Set up member permissions
- Handle user-related business logic
Member documents will automatically include these custom fields alongside the built-in authentication fields.
Best Practices
- Collection Names: Use singular form in collection definition
- Field Organization: Group related fields together
- Permissions: Start restrictive and open up as needed
- Validation: Implement thorough field validation
- Error Handling: Use try-catch blocks in hooks and custom logic
Fresh Ubuntu 24 Install
# Install nginx
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx
# Install Node
sudo apt update
sudo apt install -y nodejs
sudo apt install -y npm
sudo npm install -g n
sudo n stable
export PATH="/usr/local/bin:$PATH"
source ~/.bashrc # or source ~/.zshrc
sudo apt remove -y nodejs
node -v
# Install MongoDB
sudo apt-get install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo systemctl start mongod
sudo systemctl enable mongod
# Install PM2
sudo npm install -g pm2
pm2 startup
# Run ssh-keygen and add the public key to your keys in GitHub
# Clone your repo and build
# Add your dist folder to the nginx config at /etc/nginx/sites-available/default
# Configure Permissions
sudo chown -R www-data:www-data /root/Sites/{{repo}}/front/dist
sudo chmod -R 755 /root/Sites/{{repo}}/front/dist
sudo chmod +x /root
sudo chmod +x /root/Sites
sudo chmod +x /root/Sites/{{repo}}
sudo chmod +x /root/Sites/{{repo}}/front
# Ensure the site is working by going to the IP address in your browser
# Add your site DNS to CloudFlare and point it to the IP address
# Install Certbot
apt install certbot -y
sudo apt install python3-certbot-nginx
certbot
# Install redis
sudo apt install redis-server -y
sudo systemctl enable redis-server
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
7 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
8 months ago
8 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago