Rytass Content Management System NestJS Base Module
npm install @rytass/cms-base-nestjs-modulePowerful 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.
- [x] Multi-language article and category management
- [x] Article version control system
- [x] Hierarchical category structure (N:M relationships)
- [x] Article review and publishing workflows
- [x] Draft mode support
- [x] Full-text search functionality
- [x] Custom field support
- [x] DataLoader query performance optimization
- [x] TypeORM entities and repositories
- [x] Built-in error handling
- [x] Flexible permission control
- [x] GraphQL query support
``bash`
npm install @rytass/cms-base-nestjs-module @nestjs/typeorm typeormor
yarn add @rytass/cms-base-nestjs-module @nestjs/typeorm typeorm
`typescript
// 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 {}
`
`typescript
// 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 {}
`
`typescript
// 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);
}
}
`
`typescript
// 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,
});
}
}
`
`typescript
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
@Column({ nullable: true })
featuredImage?: string;
@Column({ type: 'text', array: true, default: [] })
tags: string[];
}
`
`typescript
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
@Column({ default: true })
isActive: boolean;
}
`
`typescript
// 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',
});
`
`typescript
// Add a new version to an article
const article = await articleService.addVersion(articleId, {
title: 'Updated Title',
content: 'Updated content',
});
// Get specific version of an article
const articleWithVersion = await articleService.findById(articleId, {
version: 2, // Specific version number
});
// Delete a version
await articleService.deleteVersion(articleId, versionNumber);
`
`typescript
// Submit article for approval
await articleService.submit(articleId, { userId });
// Approve a specific article version
await articleService.approveVersion(
{ id: articleId, version: 1 },
{ userId: managerId, signatureLevelId: 'level-2' },
);
// Reject a specific article version
await articleService.rejectVersion(
{ id: articleId },
{ userId: managerId, signatureLevelId: 'level-2', reason: 'Content needs revision' },
);
`
`typescript
import { ArticleSearchMode } from '@rytass/cms-base-nestjs-module';
// Full-text search using findCollection with searchTerm
const searchResults = await articleService.findCollection({
searchTerm: 'search keywords',
searchMode: ArticleSearchMode.FULL_TEXT, // FULL_TEXT | TITLE | TITLE_AND_TAG
offset: 0,
limit: 20,
});
// Title and tag search
const titleResults = await articleService.findCollection({
searchTerm: 'keywords',
searchMode: ArticleSearchMode.TITLE_AND_TAG,
requiredCategoryIds: ['tech-category-id'],
});
`
`typescript
// 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);
}
}
`
| 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 |
`typescript
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;
}
`
- Use DataLoader to avoid N+1 query problems
- Set up appropriate database indexes
- Use pagination for large data queries
- Enable query caching mechanisms
- Implement proper permission controls
- Validate user inputs
- Use parameterized queries to prevent SQL injection
- Log sensitive operation activities
- Separate read and write operations
- Use Redis caching for hot content
- Implement content delivery networks (CDN)
- Consider database sharding strategies
`bashGenerate migration files
npm run typeorm:migration:generate -- -n CreateCmsBaseTables
MIT