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.
npm install mango-cmsMango 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.
1. Collections
2. Fields
3. Permissions
4. Hooks
5. Endpoints
6. Plugins
7. User Configuration
8. Fresh Ubuntu Install
Collections are the foundation of Mango CMS. Each collection represents a MongoDB collection and is defined in the config/collections directory.
``javascript
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
},
}
`
- 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
Use formatRequest to modify incoming requests before they're processed:
`javascript
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
}
}
`
Mango provides several built-in field types and allows for custom field creation.
`javascript
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',
}),
},
}
`
#### Relationship Fields
`javascript`
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
`javascript${document.firstName} ${document.lastName}
{
fields: {
fullName: {
type: String,
computed: (document) => {
return `
},
// Optional: Cache expiration in seconds
expiresCache: 3600,
// Optional: Values to provide to the computed function
provide: ['firstName', 'lastName']
}
}
}
Fields can transform data on input and output using translateInput and translateOutput:
`javascript$${(value / 100).toFixed(2)}
{
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 `
}
}
}
}
Create custom fields in config/fields/ directory:
`javascript
// 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:
`javascript
import Currency from '../fields/currency'
export default {
singular: 'product',
fields: {
name: String,
price: Currency(),
salePrice: Currency(),
},
}
`
Mango provides flexible permission control at the collection level.
`javascript`
export default {
permissions: {
// Define permissions for each role in member.roles
public: ['create'],
owner: ['read', 'update', 'delete'],
},
}
`javascript
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 allow you to execute code at different stages of the CRUD operations.
`javascript`
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)
},
},
}
Create custom endpoints by exporting an object structure in config/endpoints/index.js:
`javascript
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},From: ${name} (${email})\n\n${message}
text: ,
})
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 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:
`javascript
// 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 }
}
}
}
`
Extend the built-in member collection by creating config/config/users.js:
`javascript
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.
1. Collection Names: Use singular form in collection definition
2. Field Organization: Group related fields together
3. Permissions: Start restrictive and open up as needed
4. Validation: Implement thorough field validation
5. Error Handling: Use try-catch blocks in hooks and custom logic
`Install nginx
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx