Blog Platform

Learn how to build a complete blog platform with Query-2jz, including content management, user authentication, and SEO optimization.

Project Overview

Build a full-featured blog platform with content management, user roles, comments, and advanced features.

Features

  • • Content management system (CMS)
  • • User authentication and roles
  • • Post creation and editing
  • • Categories and tags
  • • Comments and moderation
  • • SEO optimization
  • • Search functionality
  • • Analytics and insights

Project Structure

blog-platform/
├── src/
│   ├── models/
│   │   ├── User.ts
│   │   ├── Post.ts
│   │   ├── Category.ts
│   │   ├── Tag.ts
│   │   ├── Comment.ts
│   │   └── Media.ts
│   ├── services/
│   │   ├── PostService.ts
│   │   ├── UserService.ts
│   │   ├── CommentService.ts
│   │   └── SearchService.ts
│   ├── controllers/
│   │   ├── PostController.ts
│   │   ├── UserController.ts
│   │   └── CommentController.ts
│   ├── middleware/
│   │   ├── auth.ts
│   │   ├── validation.ts
│   │   └── seo.ts
│   ├── config/
│   │   └── query-2jz.config.ts
│   └── index.ts
├── package.json
├── tsconfig.json
└── README.md

Data Models

Define comprehensive data models for the blog platform.

Post Model

// src/models/Post.ts
export interface Post {
  id: string;
  title: string;
  slug: string;
  excerpt: string;
  content: string;
  featuredImage?: string;
  status: 'draft' | 'published' | 'archived';
  visibility: 'public' | 'private' | 'password';
  password?: string;
  authorId: string;
  categoryId?: string;
  tags: string[];
  seo: {
    title?: string;
    description?: string;
    keywords?: string[];
    canonicalUrl?: string;
    ogImage?: string;
  };
  readingTime: number;
  wordCount: number;
  viewCount: number;
  likeCount: number;
  commentCount: number;
  publishedAt?: Date;
  scheduledAt?: Date;
  createdAt: Date;
  updatedAt: Date;
  author?: User;
  category?: Category;
  comments?: Comment[];
  media?: Media[];
}

export interface Category {
  id: string;
  name: string;
  slug: string;
  description?: string;
  color?: string;
  icon?: string;
  parentId?: string;
  isActive: boolean;
  postCount: number;
  createdAt: Date;
  updatedAt: Date;
  parent?: Category;
  children?: Category[];
  posts?: Post[];
}

export interface Tag {
  id: string;
  name: string;
  slug: string;
  description?: string;
  color?: string;
  postCount: number;
  createdAt: Date;
  updatedAt: Date;
  posts?: Post[];
}

export interface Comment {
  id: string;
  postId: string;
  authorId?: string;
  authorName: string;
  authorEmail: string;
  authorUrl?: string;
  content: string;
  status: 'pending' | 'approved' | 'spam' | 'trash';
  parentId?: string;
  ipAddress: string;
  userAgent?: string;
  isAuthor: boolean;
  createdAt: Date;
  updatedAt: Date;
  post?: Post;
  author?: User;
  parent?: Comment;
  replies?: Comment[];
}

export interface Media {
  id: string;
  filename: string;
  originalName: string;
  mimeType: string;
  size: number;
  url: string;
  thumbnailUrl?: string;
  alt?: string;
  caption?: string;
  uploadedBy: string;
  postId?: string;
  createdAt: Date;
  updatedAt: Date;
  uploader?: User;
  post?: Post;
}

User Model

// src/models/User.ts
export interface User {
  id: string;
  username: string;
  email: string;
  displayName: string;
  firstName?: string;
  lastName?: string;
  avatar?: string;
  bio?: string;
  website?: string;
  location?: string;
  role: 'admin' | 'editor' | 'author' | 'contributor' | 'subscriber';
  status: 'active' | 'inactive' | 'suspended';
  emailVerified: boolean;
  lastLoginAt?: Date;
  preferences: {
    theme: 'light' | 'dark';
    language: string;
    timezone: string;
    notifications: {
      email: boolean;
      push: boolean;
      comments: boolean;
      posts: boolean;
    };
  };
  social: {
    twitter?: string;
    facebook?: string;
    instagram?: string;
    linkedin?: string;
    github?: string;
  };
  stats: {
    postCount: number;
    commentCount: number;
    viewCount: number;
    likeCount: number;
  };
  createdAt: Date;
  updatedAt: Date;
  posts?: Post[];
  comments?: Comment[];
  media?: Media[];
}

Query-2jz Configuration

Configure Query-2jz with all the necessary models and relationships for the blog platform.

// src/config/query-2jz.config.ts
import { Query-2jzConfig } from 'query-2jz';

export const query-2jzConfig: Query-2jzConfig = {
  database: {
    type: 'postgresql',
    connection: process.env.DATABASE_URL
  },
  cache: {
    type: 'redis',
    connection: process.env.REDIS_URL
  },
  models: [
    {
      name: 'User',
      fields: {
        id: { type: 'id' },
        username: { type: 'string', required: true, unique: true },
        email: { type: 'email', required: true, unique: true },
        displayName: { type: 'string', required: true },
        firstName: { type: 'string' },
        lastName: { type: 'string' },
        avatar: { type: 'string' },
        bio: { type: 'text' },
        website: { type: 'string' },
        location: { type: 'string' },
        role: { type: 'enum', values: ['admin', 'editor', 'author', 'contributor', 'subscriber'], default: 'subscriber' },
        status: { type: 'enum', values: ['active', 'inactive', 'suspended'], default: 'active' },
        emailVerified: { type: 'boolean', default: false },
        lastLoginAt: { type: 'date' },
        preferences: { type: 'json', default: {} },
        social: { type: 'json', default: {} },
        stats: { type: 'json', default: { postCount: 0, commentCount: 0, viewCount: 0, likeCount: 0 } },
        createdAt: { type: 'date', default: 'now' },
        updatedAt: { type: 'date', default: 'now' },
        posts: { type: 'relation', model: 'Post', many: true, foreignKey: 'authorId' },
        comments: { type: 'relation', model: 'Comment', many: true, foreignKey: 'authorId' }
      },
      indexes: ['username', 'email', 'role', 'status']
    },
    {
      name: 'Post',
      fields: {
        id: { type: 'id' },
        title: { type: 'string', required: true },
        slug: { type: 'string', required: true, unique: true },
        excerpt: { type: 'text' },
        content: { type: 'text', required: true },
        featuredImage: { type: 'string' },
        status: { type: 'enum', values: ['draft', 'published', 'archived'], default: 'draft' },
        visibility: { type: 'enum', values: ['public', 'private', 'password'], default: 'public' },
        password: { type: 'string' },
        authorId: { type: 'string', required: true },
        categoryId: { type: 'string' },
        tags: { type: 'json', default: [] },
        seo: { type: 'json', default: {} },
        readingTime: { type: 'number', default: 0 },
        wordCount: { type: 'number', default: 0 },
        viewCount: { type: 'number', default: 0 },
        likeCount: { type: 'number', default: 0 },
        commentCount: { type: 'number', default: 0 },
        publishedAt: { type: 'date' },
        scheduledAt: { type: 'date' },
        createdAt: { type: 'date', default: 'now' },
        updatedAt: { type: 'date', default: 'now' },
        author: { type: 'relation', model: 'User', foreignKey: 'authorId' },
        category: { type: 'relation', model: 'Category', foreignKey: 'categoryId' },
        comments: { type: 'relation', model: 'Comment', many: true, foreignKey: 'postId' }
      },
      indexes: ['slug', 'status', 'authorId', 'categoryId', 'publishedAt', 'tags']
    },
    {
      name: 'Category',
      fields: {
        id: { type: 'id' },
        name: { type: 'string', required: true },
        slug: { type: 'string', required: true, unique: true },
        description: { type: 'text' },
        color: { type: 'string' },
        icon: { type: 'string' },
        parentId: { type: 'string' },
        isActive: { type: 'boolean', default: true },
        postCount: { type: 'number', default: 0 },
        createdAt: { type: 'date', default: 'now' },
        updatedAt: { type: 'date', default: 'now' },
        parent: { type: 'relation', model: 'Category', foreignKey: 'parentId' },
        children: { type: 'relation', model: 'Category', many: true, foreignKey: 'parentId' },
        posts: { type: 'relation', model: 'Post', many: true, foreignKey: 'categoryId' }
      },
      indexes: ['slug', 'parentId', 'isActive']
    },
    {
      name: 'Comment',
      fields: {
        id: { type: 'id' },
        postId: { type: 'string', required: true },
        authorId: { type: 'string' },
        authorName: { type: 'string', required: true },
        authorEmail: { type: 'email', required: true },
        authorUrl: { type: 'string' },
        content: { type: 'text', required: true },
        status: { type: 'enum', values: ['pending', 'approved', 'spam', 'trash'], default: 'pending' },
        parentId: { type: 'string' },
        ipAddress: { type: 'string', required: true },
        userAgent: { type: 'string' },
        isAuthor: { type: 'boolean', default: false },
        createdAt: { type: 'date', default: 'now' },
        updatedAt: { type: 'date', default: 'now' },
        post: { type: 'relation', model: 'Post', foreignKey: 'postId' },
        author: { type: 'relation', model: 'User', foreignKey: 'authorId' },
        parent: { type: 'relation', model: 'Comment', foreignKey: 'parentId' },
        replies: { type: 'relation', model: 'Comment', many: true, foreignKey: 'parentId' }
      },
      indexes: ['postId', 'status', 'authorId', 'parentId', 'createdAt']
    }
  ]
};

Post Service

Implement business logic for post management and content operations.

Post Service Implementation

// src/services/PostService.ts
import { Query-2jz } from 'query-2jz';
import { Post } from '../models/Post';

export class PostService {
  constructor(private query-2jz: Query-2jz) {}

  async createPost(data: {
    title: string;
    content: string;
    excerpt?: string;
    authorId: string;
    categoryId?: string;
    tags?: string[];
    status?: string;
    visibility?: string;
    seo?: any;
  }) {
    const slug = this.generateSlug(data.title);
    
    // Calculate reading time and word count
    const wordCount = this.countWords(data.content);
    const readingTime = Math.ceil(wordCount / 200); // 200 words per minute

    const post = await this.query-2jz.create({
      model: 'Post',
      data: {
        title: data.title,
        slug,
        content: data.content,
        excerpt: data.excerpt || this.generateExcerpt(data.content),
        authorId: data.authorId,
        categoryId: data.categoryId,
        tags: data.tags || [],
        status: data.status || 'draft',
        visibility: data.visibility || 'public',
        seo: data.seo || {},
        readingTime,
        wordCount
      }
    });

    // Update category post count
    if (data.categoryId) {
      await this.updateCategoryPostCount(data.categoryId);
    }

    return post;
  }

  async updatePost(id: string, data: Partial<Post>) {
    const existingPost = await this.query-2jz.query({
      model: 'Post',
      where: { id }
    });

    if (existingPost.data.length === 0) {
      throw new Error('Post not found');
    }

    const updateData: any = { ...data };

    // Regenerate slug if title changed
    if (data.title && data.title !== existingPost.data[0].title) {
      updateData.slug = this.generateSlug(data.title);
    }

    // Recalculate reading time and word count if content changed
    if (data.content) {
      updateData.wordCount = this.countWords(data.content);
      updateData.readingTime = Math.ceil(updateData.wordCount / 200);
    }

    const updatedPost = await this.query-2jz.update({
      model: 'Post',
      id,
      data: updateData
    });

    return updatedPost;
  }

  async publishPost(id: string) {
    const post = await this.query-2jz.query({
      model: 'Post',
      where: { id }
    });

    if (post.data.length === 0) {
      throw new Error('Post not found');
    }

    return this.query-2jz.update({
      model: 'Post',
      id,
      data: {
        status: 'published',
        publishedAt: new Date()
      }
    });
  }

  async getPosts(filters: {
    status?: string;
    authorId?: string;
    categoryId?: string;
    tags?: string[];
    search?: string;
    limit?: number;
    offset?: number;
  }) {
    const where: any = {};

    if (filters.status) {
      where.status = filters.status;
    }

    if (filters.authorId) {
      where.authorId = filters.authorId;
    }

    if (filters.categoryId) {
      where.categoryId = filters.categoryId;
    }

    if (filters.tags && filters.tags.length > 0) {
      where.tags = { $overlap: filters.tags };
    }

    if (filters.search) {
      where.$or = [
        { title: { $like: `%${filters.search}%` } },
        { content: { $like: `%${filters.search}%` } },
        { excerpt: { $like: `%${filters.search}%` } }
      ];
    }

    return this.query-2jz.query({
      model: 'Post',
      where,
      include: ['author', 'category', 'comments'],
      orderBy: 'publishedAt:desc',
      limit: filters.limit || 10,
      offset: filters.offset || 0
    });
  }

  async getPost(slug: string) {
    const post = await this.query-2jz.query({
      model: 'Post',
      where: { slug },
      include: ['author', 'category', 'comments', 'comments.author']
    });

    if (post.data.length === 0) {
      throw new Error('Post not found');
    }

    // Increment view count
    await this.incrementViewCount(post.data[0].id);

    return post.data[0];
  }

  async getRelatedPosts(postId: string, limit = 5) {
    const post = await this.query-2jz.query({
      model: 'Post',
      where: { id: postId }
    });

    if (post.data.length === 0) {
      return [];
    }

    const postData = post.data[0];
    const where: any = {
      id: { $ne: postId },
      status: 'published'
    };

    // Find posts with same category or tags
    if (postData.categoryId) {
      where.categoryId = postData.categoryId;
    }

    if (postData.tags && postData.tags.length > 0) {
      where.tags = { $overlap: postData.tags };
    }

    const relatedPosts = await this.query-2jz.query({
      model: 'Post',
      where,
      include: ['author', 'category'],
      orderBy: 'publishedAt:desc',
      limit
    });

    return relatedPosts.data;
  }

  async incrementViewCount(postId: string) {
    const post = await this.query-2jz.query({
      model: 'Post',
      where: { id: postId }
    });

    if (post.data.length > 0) {
      await this.query-2jz.update({
        model: 'Post',
        id: postId,
        data: {
          viewCount: post.data[0].viewCount + 1
        }
      });
    }
  }

  async updateCategoryPostCount(categoryId: string) {
    const posts = await this.query-2jz.query({
      model: 'Post',
      where: { categoryId, status: 'published' }
    });

    await this.query-2jz.update({
      model: 'Category',
      id: categoryId,
      data: {
        postCount: posts.meta.total
      }
    });
  }

  private generateSlug(title: string): string {
    return title
      .toLowerCase()
      .replace(/[^a-z0-9 -]/g, '')
      .replace(/s+/g, '-')
      .replace(/-+/g, '-')
      .trim('-');
  }

  private generateExcerpt(content: string, length = 160): string {
    const plainText = content.replace(/<[^>]*>/g, '');
    return plainText.length > length 
      ? plainText.substring(0, length) + '...'
      : plainText;
  }

  private countWords(text: string): number {
    const plainText = text.replace(/<[^>]*>/g, '');
    return plainText.split(/s+/).filter(word => word.length > 0).length;
  }
}

Comment Service

Implement comment management with moderation and spam detection.

// src/services/CommentService.ts
import { Query-2jz } from 'query-2jz';
import { Comment } from '../models/Comment';

export class CommentService {
  constructor(private query-2jz: Query-2jz) {}

  async createComment(data: {
    postId: string;
    authorId?: string;
    authorName: string;
    authorEmail: string;
    authorUrl?: string;
    content: string;
    parentId?: string;
    ipAddress: string;
    userAgent?: string;
  }) {
    // Check if user is the post author
    const post = await this.query-2jz.query({
      model: 'Post',
      where: { id: data.postId },
      include: ['author']
    });

    if (post.data.length === 0) {
      throw new Error('Post not found');
    }

    const isAuthor = data.authorId === post.data[0].authorId;
    
    // Determine comment status based on moderation rules
    const status = this.determineCommentStatus(data, isAuthor);

    const comment = await this.query-2jz.create({
      model: 'Comment',
      data: {
        postId: data.postId,
        authorId: data.authorId,
        authorName: data.authorName,
        authorEmail: data.authorEmail,
        authorUrl: data.authorUrl,
        content: data.content,
        status,
        parentId: data.parentId,
        ipAddress: data.ipAddress,
        userAgent: data.userAgent,
        isAuthor
      }
    });

    // Update post comment count
    await this.updatePostCommentCount(data.postId);

    return comment;
  }

  async getComments(postId: string, status = 'approved') {
    return this.query-2jz.query({
      model: 'Comment',
      where: { 
        postId,
        status,
        parentId: null // Only top-level comments
      },
      include: ['author', 'replies', 'replies.author'],
      orderBy: 'createdAt:asc'
    });
  }

  async getCommentReplies(commentId: string) {
    return this.query-2jz.query({
      model: 'Comment',
      where: { 
        parentId: commentId,
        status: 'approved'
      },
      include: ['author'],
      orderBy: 'createdAt:asc'
    });
  }

  async moderateComment(commentId: string, status: 'approved' | 'spam' | 'trash') {
    const comment = await this.query-2jz.query({
      model: 'Comment',
      where: { id: commentId }
    });

    if (comment.data.length === 0) {
      throw new Error('Comment not found');
    }

    const updatedComment = await this.query-2jz.update({
      model: 'Comment',
      id: commentId,
      data: { status }
    });

    // Update post comment count
    await this.updatePostCommentCount(comment.data[0].postId);

    return updatedComment;
  }

  async deleteComment(commentId: string) {
    const comment = await this.query-2jz.query({
      model: 'Comment',
      where: { id: commentId }
    });

    if (comment.data.length === 0) {
      throw new Error('Comment not found');
    }

    // Delete comment and all replies
    await this.query-2jz.delete({
      model: 'Comment',
      where: { 
        $or: [
          { id: commentId },
          { parentId: commentId }
        ]
      }
    });

    // Update post comment count
    await this.updatePostCommentCount(comment.data[0].postId);
  }

  async getPendingComments(limit = 20, offset = 0) {
    return this.query-2jz.query({
      model: 'Comment',
      where: { status: 'pending' },
      include: ['post', 'author'],
      orderBy: 'createdAt:desc',
      limit,
      offset
    });
  }

  async getSpamComments(limit = 20, offset = 0) {
    return this.query-2jz.query({
      model: 'Comment',
      where: { status: 'spam' },
      include: ['post', 'author'],
      orderBy: 'createdAt:desc',
      limit,
      offset
    });
  }

  async bulkModerateComments(commentIds: string[], status: 'approved' | 'spam' | 'trash') {
    const comments = await this.query-2jz.query({
      model: 'Comment',
      where: { id: { $in: commentIds } }
    });

    for (const comment of comments.data) {
      await this.query-2jz.update({
        model: 'Comment',
        id: comment.id,
        data: { status }
      });

      // Update post comment count
      await this.updatePostCommentCount(comment.postId);
    }

    return comments.data;
  }

  private determineCommentStatus(data: any, isAuthor: boolean): string {
    // Auto-approve comments from post authors
    if (isAuthor) {
      return 'approved';
    }

    // Check for spam indicators
    if (this.isSpam(data)) {
      return 'spam';
    }

    // Check if commenter has approved comments before
    const hasApprovedComments = this.query-2jz.query({
      model: 'Comment',
      where: { 
        authorEmail: data.authorEmail,
        status: 'approved'
      },
      limit: 1
    });

    // Auto-approve if user has approved comments
    if (hasApprovedComments) {
      return 'approved';
    }

    // Default to pending for moderation
    return 'pending';
  }

  private isSpam(data: any): boolean {
    const spamKeywords = ['viagra', 'casino', 'loan', 'free money'];
    const content = data.content.toLowerCase();
    
    // Check for spam keywords
    if (spamKeywords.some(keyword => content.includes(keyword))) {
      return true;
    }

    // Check for excessive links
    const linkCount = (content.match(/https?:///g) || []).length;
    if (linkCount > 2) {
      return true;
    }

    // Check for excessive caps
    const capsRatio = (content.match(/[A-Z]/g) || []).length / content.length;
    if (capsRatio > 0.7) {
      return true;
    }

    return false;
  }

  private async updatePostCommentCount(postId: string) {
    const comments = await this.query-2jz.query({
      model: 'Comment',
      where: { 
        postId,
        status: 'approved'
      }
    });

    await this.query-2jz.update({
      model: 'Post',
      id: postId,
      data: {
        commentCount: comments.meta.total
      }
    });
  }
}

API Controllers

Create REST API controllers for the blog platform.

Post Controller

// src/controllers/PostController.ts
import { Request, Response } from 'express';
import { PostService } from '../services/PostService';

export class PostController {
  constructor(private postService: PostService) {}

  async getPosts(req: Request, res: Response) {
    try {
      const {
        status = 'published',
        authorId,
        categoryId,
        tags,
        search,
        limit = 10,
        offset = 0
      } = req.query;

      const posts = await this.postService.getPosts({
        status: status as string,
        authorId: authorId as string,
        categoryId: categoryId as string,
        tags: tags ? (tags as string).split(',') : undefined,
        search: search as string,
        limit: parseInt(limit as string),
        offset: parseInt(offset as string)
      });

      res.json(posts);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async getPost(req: Request, res: Response) {
    try {
      const { slug } = req.params;
      const post = await this.postService.getPost(slug);
      res.json(post);
    } catch (error) {
      res.status(404).json({ error: error.message });
    }
  }

  async getRelatedPosts(req: Request, res: Response) {
    try {
      const { id } = req.params;
      const { limit = 5 } = req.query;
      
      const relatedPosts = await this.postService.getRelatedPosts(
        id,
        parseInt(limit as string)
      );
      
      res.json(relatedPosts);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async createPost(req: Request, res: Response) {
    try {
      const userId = req.user.id;
      const postData = req.body;
      
      const post = await this.postService.createPost({
        ...postData,
        authorId: userId
      });
      
      res.status(201).json(post);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async updatePost(req: Request, res: Response) {
    try {
      const { id } = req.params;
      const postData = req.body;
      
      const post = await this.postService.updatePost(id, postData);
      res.json(post);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async publishPost(req: Request, res: Response) {
    try {
      const { id } = req.params;
      const post = await this.postService.publishPost(id);
      res.json(post);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async deletePost(req: Request, res: Response) {
    try {
      const { id } = req.params;
      await this.query-2jz.delete({ model: 'Post', id });
      res.status(204).send();
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }
}

Comment Controller

// src/controllers/CommentController.ts
import { Request, Response } from 'express';
import { CommentService } from '../services/CommentService';

export class CommentController {
  constructor(private commentService: CommentService) {}

  async getComments(req: Request, res: Response) {
    try {
      const { postId } = req.params;
      const { status = 'approved' } = req.query;
      
      const comments = await this.commentService.getComments(
        postId,
        status as string
      );
      
      res.json(comments);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async createComment(req: Request, res: Response) {
    try {
      const { postId } = req.params;
      const commentData = {
        ...req.body,
        postId,
        ipAddress: req.ip,
        userAgent: req.get('User-Agent')
      };
      
      const comment = await this.commentService.createComment(commentData);
      res.status(201).json(comment);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async moderateComment(req: Request, res: Response) {
    try {
      const { id } = req.params;
      const { status } = req.body;
      
      const comment = await this.commentService.moderateComment(id, status);
      res.json(comment);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async getPendingComments(req: Request, res: Response) {
    try {
      const { limit = 20, offset = 0 } = req.query;
      
      const comments = await this.commentService.getPendingComments(
        parseInt(limit as string),
        parseInt(offset as string)
      );
      
      res.json(comments);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }

  async bulkModerateComments(req: Request, res: Response) {
    try {
      const { commentIds, status } = req.body;
      
      const comments = await this.commentService.bulkModerateComments(
        commentIds,
        status
      );
      
      res.json(comments);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  }
}

SEO Optimization

Implement SEO features for better search engine visibility.

// src/middleware/seo.ts
import { Request, Response, NextFunction } from 'express';
import { Query-2jz } from 'query-2jz';

export class SEOMiddleware {
  constructor(private query-2jz: Query-2jz) {}

  generateSitemap = async (req: Request, res: Response) => {
    try {
      const posts = await this.query-2jz.query({
        model: 'Post',
        where: { status: 'published' },
        select: ['slug', 'updatedAt']
      });

      const categories = await this.query-2jz.query({
        model: 'Category',
        where: { isActive: true },
        select: ['slug', 'updatedAt']
      });

      const sitemap = this.buildSitemap(posts.data, categories.data);
      
      res.set('Content-Type', 'application/xml');
      res.send(sitemap);
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  };

  generateRobotsTxt = (req: Request, res: Response) => {
    const robotsTxt = `User-agent: *
Allow: /
Disallow: /admin/
Disallow: /api/
Sitemap: ${req.protocol}://${req.get('host')}/sitemap.xml`;

    res.set('Content-Type', 'text/plain');
    res.send(robotsTxt);
  };

  private buildSitemap(posts: any[], categories: any[]): string {
    const baseUrl = process.env.BASE_URL || 'https://example.com';
    
    let sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;

    // Add homepage
    sitemap += `
  <url>
    <loc>${baseUrl}</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>`;

    // Add posts
    posts.forEach(post => {
      sitemap += `
  <url>
    <loc>${baseUrl}/posts/${post.slug}</loc>
    <lastmod>${post.updatedAt}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>`;
    });

    // Add categories
    categories.forEach(category => {
      sitemap += `
  <url>
    <loc>${baseUrl}/categories/${category.slug}</loc>
    <lastmod>${category.updatedAt}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.6</priority>
  </url>`;
    });

    sitemap += `
</urlset>`;

    return sitemap;
  }
}

Next Steps

Enhance your blog platform with additional features.

Advanced Features

  • • Advanced search with filters
  • • Content scheduling and automation
  • • Email newsletters and subscriptions
  • • Social media integration
  • • Analytics and insights
  • • Content versioning and history
  • • Multi-language support