Package Exports
- @rytass/cms-base-nestjs-module
- @rytass/cms-base-nestjs-module/index.cjs.js
- @rytass/cms-base-nestjs-module/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@rytass/cms-base-nestjs-module) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Rytass Utils - CMS Base NestJS Module
Powerful content management system base module designed specifically for NestJS applications. Provides complete multi-language article management, version control, approval workflows, and category management functionality, making it the ideal choice for building enterprise-grade CMS systems.
Features
- Multi-language article and category management
- Article version control system
- Hierarchical category structure (N:M relationships)
- Article review and publishing workflows
- Draft mode support
- Full-text search functionality
- Custom field support
- DataLoader query performance optimization
- TypeORM entities and repositories
- Built-in error handling
- Flexible permission control
- GraphQL query support
Installation
npm install @rytass/cms-base-nestjs-module @nestjs/typeorm typeorm
# or
yarn add @rytass/cms-base-nestjs-module @nestjs/typeorm typeorm
Basic Usage
Module Configuration
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CmsBaseModule } from '@rytass/cms-base-nestjs-module';
@Module({
imports: [
TypeOrmModule.forRoot({
// Database configuration
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'username',
password: 'password',
database: 'cms_database',
entities: [/* other entities */],
synchronize: true, // Development environment only
}),
CmsBaseModule.forRoot({
multipleLanguageMode: true,
draftMode: true,
signatureLevels: [
{ id: 1, name: 'Editor', level: 1 },
{ id: 2, name: 'Senior Editor', level: 2 },
{ id: 3, name: 'Chief Editor', level: 3 }
],
fullTextSearchMode: true,
autoReleaseAfterApproved: false
})
],
})
export class AppModule {}
Async Configuration
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CmsBaseModule } from '@rytass/cms-base-nestjs-module';
@Module({
imports: [
ConfigModule.forRoot(),
CmsBaseModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
multipleLanguageMode: configService.get('CMS_MULTI_LANGUAGE') === 'true',
draftMode: configService.get('CMS_DRAFT_MODE') === 'true',
signatureLevels: JSON.parse(configService.get('CMS_SIGNATURE_LEVELS')),
fullTextSearchMode: configService.get('CMS_FULL_TEXT_SEARCH') === 'true'
})
})
],
})
export class AppModule {}
Core Services
Article Management
// article.service.ts
import { Injectable } from '@nestjs/common';
import { ArticleBaseService } from '@rytass/cms-base-nestjs-module';
@Injectable()
export class ArticleService {
constructor(
private readonly articleBaseService: ArticleBaseService
) {}
async createArticle(data: CreateArticleDto) {
return await this.articleBaseService.create({
title: data.title,
content: data.content,
categoryIds: data.categoryIds,
language: data.language || 'zh-TW',
authorId: data.authorId,
customFields: data.customFields
});
}
async publishArticle(articleId: string, userId: string) {
return await this.articleBaseService.release(articleId, { userId });
}
async getArticleWithVersions(articleId: string) {
return await this.articleBaseService.findById(articleId);
}
}
Category Management
// category.service.ts
import { Injectable } from '@nestjs/common';
import { CategoryBaseService } from '@rytass/cms-base-nestjs-module';
@Injectable()
export class CategoryService {
constructor(
private readonly categoryBaseService: CategoryBaseService
) {}
async createCategory(data: CreateCategoryDto) {
return await this.categoryBaseService.create({
names: data.names, // Multi-language names
parentId: data.parentId,
description: data.description,
sortOrder: data.sortOrder
});
}
async getCategoryTree() {
return await this.categoryBaseService.findAll();
}
async getArticlesByCategory(categoryId: string, options = {}) {
return await this.categoryBaseService.findArticles(
categoryId,
{
page: options.page || 1,
limit: options.limit || 10,
includeSubCategories: true
}
);
}
}
Data Models
Article Entity
import { BaseArticleEntity } from '@rytass/cms-base-nestjs-module';
import { Entity, Column } from 'typeorm';
@Entity('articles')
export class Article extends BaseArticleEntity {
@Column({ type: 'jsonb', nullable: true })
customFields?: Record<string, any>;
@Column({ nullable: true })
featuredImage?: string;
@Column({ type: 'text', array: true, default: [] })
tags: string[];
}
Category Entity
import { BaseCategoryEntity } from '@rytass/cms-base-nestjs-module';
import { Entity, Column } from 'typeorm';
@Entity('categories')
export class Category extends BaseCategoryEntity {
@Column({ nullable: true })
icon?: string;
@Column({ type: 'jsonb', nullable: true })
metadata?: Record<string, any>;
@Column({ default: true })
isActive: boolean;
}
Multi-Language Support
// Create multi-language article
const article = await articleService.createArticle({
title: {
'zh-TW': '中文標題',
'en-US': 'English Title',
'ja-JP': '日本語タイトル'
},
content: {
'zh-TW': '中文內容...',
'en-US': 'English content...',
'ja-JP': '日本語の内容...'
},
categoryIds: ['category-1', 'category-2'],
language: 'zh-TW' // Primary language
});
// Query articles in specific language
const articles = await articleService.findArticles({
language: 'en-US',
status: 'published'
});
Version Control
// Create article version
const version = await articleService.createVersion(articleId, {
title: 'Updated Title',
content: 'Updated content',
changeReason: 'Fixed typos',
authorId: userId
});
// Get version history
const versions = await articleService.getVersionHistory(articleId);
// Revert to specific version
await articleService.revertToVersion(articleId, versionId);
// Compare version differences
const diff = await articleService.compareVersions(articleId, version1Id, version2Id);
Approval Workflow
// Submit article for approval
const signatureRequest = await articleService.submitForApproval(articleId, {
submitterId: userId,
comments: 'Please review this article'
});
// Approve article
await articleService.approveArticle(articleId, {
approverId: managerId,
level: 2, // Approval level
comments: 'Content looks good, approved'
});
// Reject article
await articleService.rejectArticle(articleId, {
reviewerId: managerId,
reason: 'Content needs revision',
suggestions: 'Please add more details'
});
Full-Text Search
// Full-text search
const searchResults = await articleService.searchArticles({
query: 'search keywords',
language: 'zh-TW',
categories: ['tech', 'news'],
dateRange: {
from: new Date('2024-01-01'),
to: new Date('2024-12-31')
},
page: 1,
limit: 20
});
// Search suggestions (auto-complete)
const suggestions = await articleService.getSuggestions('keywords');
DataLoader Integration
// Use DataLoader to optimize N+1 query problems
import { ArticleDataloader, CategoryDataloader } from '@rytass/cms-base-nestjs-module';
@Injectable()
export class ArticleResolver {
constructor(
private readonly articleDataloader: ArticleDataloader,
private readonly categoryDataloader: CategoryDataloader
) {}
@ResolveField()
async categories(@Parent() article: Article) {
return this.categoryDataloader.loadMany(article.categoryIds);
}
@ResolveField()
async relatedArticles(@Parent() article: Article) {
return this.articleDataloader.loadRelated(article.id);
}
}
Configuration Options
CmsBaseModuleOptions
Option | Type | Default | Description |
---|---|---|---|
multipleLanguageMode |
boolean |
false |
Enable multi-language support |
draftMode |
boolean |
true |
Enable draft mode |
signatureLevels |
SignatureLevel[] |
[] |
Approval level settings |
fullTextSearchMode |
boolean |
false |
Enable full-text search |
autoReleaseAfterApproved |
boolean |
false |
Auto-publish after approval |
circularCategoryMode |
boolean |
false |
Allow circular category references |
multipleCategoryParentMode |
boolean |
false |
Allow multiple parent categories |
Error Handling
import {
ArticleNotFoundError,
CategoryNotFoundError,
InsufficientPermissionError
} from '@rytass/cms-base-nestjs-module';
try {
await articleService.publishArticle(articleId, userId);
} catch (error) {
if (error instanceof ArticleNotFoundError) {
throw new NotFoundException('Article not found');
} else if (error instanceof InsufficientPermissionError) {
throw new ForbiddenException('Insufficient permissions');
}
throw error;
}
Best Practices
Performance
- Use DataLoader to avoid N+1 query problems
- Set up appropriate database indexes
- Use pagination for large data queries
- Enable query caching mechanisms
Security
- Implement proper permission controls
- Validate user inputs
- Use parameterized queries to prevent SQL injection
- Log sensitive operation activities
Scalability
- Separate read and write operations
- Use Redis caching for hot content
- Implement content delivery networks (CDN)
- Consider database sharding strategies
Migration
# Generate migration files
npm run typeorm:migration:generate -- -n CreateCmsBaseTables
# Run migrations
npm run typeorm:migration:run
# Revert migrations
npm run typeorm:migration:revert
License
MIT