Skip to content

PortlandKyGuy/express-simple-static-blog

Repository files navigation

express-simple-static-blog

npm version License: MIT Node.js Version

A simple, configurable static blog system for Express.js applications. This library allows you to quickly add a blog to your Express app using static HTML files with minimal configuration. It isn't intended to be a long term solution. More of a quick answer to get up some blogs on your site while you figure out your long-term blog strategies.

This is not an automated CMS tool. You will need to deploy and/or stop-and-start your service each time you add a blog.

Features

  • 📁 File-based blog posts (HTML files)
  • 🚀 In-memory caching for fast performance
  • 🎨 Clean separation - you own the templates
  • 📅 Date-based URL routing
  • 🔍 Built-in search functionality
  • 📄 Pagination support
  • 📡 RSS feed generation
  • 🌐 JSON API endpoints
  • ⚡ Zero database dependency

Why Use This?

  • Simple: No database setup, no complex configuration. Just HTML files.
  • Fast: In-memory caching means your blog loads quickly.
  • Flexible: Use your own templates and styling - this library just handles the blog logic.
  • SEO-Friendly: Clean URLs, RSS feeds, and static content that search engines love.
  • Developer-Friendly: Built for Express.js developers who want to add a blog without the bloat.

Installation

npm install express-simple-static-blog

Quick Start

Basic Setup (Minimal Configuration)

const express = require('express');
const expressStaticBlog = require('express-simple-static-blog');

const app = express();

// Set up view engine and views directory
app.set('view engine', 'ejs');
app.set('views', './views');

// Initialize the blog system
const blog = expressStaticBlog({
    blogsDir: './views/blogs',      // Directory containing blog HTML files
    routePrefix: '/blogs'           // URL prefix for blog routes
});

// Mount the blog router
app.use(blog.router());

app.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});

Important: With minimal configuration, you must:

  1. Create a views/blogs.ejs template file (note: default expects .ejs extension)
  2. Your blog HTML files will be sent as-is (no template processing for detail pages)

Complete Examples

Example 1: HTML Files with EJS Includes (Recommended for existing HTML blogs)

This approach lets you use HTML blog files while maintaining consistent site layout.

app.js:

const express = require('express');
const expressStaticBlog = require('express-simple-static-blog');

const app = express();

app.set('view engine', 'ejs');
app.set('views', './views');
app.engine('.html', require('ejs').renderFile);

const blog = expressStaticBlog({
    blogsDir: './views/blogs',
    routePrefix: '/blogs',
    listTemplate: 'blogs.html',       // Use .html extension
    contentFormat: 'body-only',       // Extract body content only
    detailTemplate: 'blog-detail.html' // Template for blog details
});

app.use(blog.router());

views/blogs.html:

<!DOCTYPE html>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <%- include('partials/navigation') %>
    
    <h1>Blog Posts</h1>
    <% blogs.forEach(blog => { %>
        <article>
            <h2><a href="/blogs/<%= blog.year %>-<%= blog.month %>-<%= blog.day %>">
                <%= blog.title %>
            </a></h2>
            <time><%= blog.date %></time>
            <p><%= blog.desc %></p>
        </article>
    <% }); %>
    
    <%- include('partials/footer') %>
</body>
</html>

views/blog-detail.html:

<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
</head>
<body>
    <%- include('partials/navigation') %>
    
    <%- renderedContent %>
    
    <%- include('partials/footer') %>
</body>
</html>

views/blogs/2025-01-15-first-post.html:

<!DOCTYPE html>
<html>
<head>
    <title>My First Post</title>
    <!-- [date: 2025-01-15] -->
    <!-- [desc: Introduction to my blog] -->
</head>
<body>
    <h1>My First Post</h1>
    <p>Welcome to my blog!</p>
</body>
</html>

Example 2: Markdown Blog with Front Matter

app.js:

const blog = expressStaticBlog({
    blogsDir: './content/blog',
    contentFormat: 'markdown',
    metadataFormat: 'front-matter',
    listTemplate: 'blog/index',
    detailTemplate: 'blog/post'
});

content/blog/2025-01-15-getting-started.md:

---
date: 2025-01-15
title: Getting Started with Node.js
description: A beginner's guide to Node.js
---

# Getting Started with Node.js

Node.js is a powerful JavaScript runtime...

## Installation

First, download Node.js from the official website...

Example 3: Simple HTML Blog (Legacy/Backwards Compatible)

If you want blog HTML files to be served directly without any template processing:

const blog = expressStaticBlog({
    blogsDir: './blogs',
    routePrefix: '/blog',
    listTemplate: 'blog-list'  // Only the list uses a template
    // No contentFormat or detailTemplate specified
});

Blog File Format

Blog posts are HTML files with metadata in the Title element and HTML comments for 'date' and 'desc':

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My First Blog Post</title>  
    <!-- [date: 2025-08-15] -->
    <!-- [desc: This is a description of my blog post.] -->
</head>
<body>
    <h1>My First Blog Post</h1>
    <p>Your blog content goes here...</p>
</body>
</html>

Configuration Options

const blog = expressStaticBlog({
    // Required
    blogsDir: './blogs',            // Path to blog HTML files
    
    // Optional
    cache: true,                    // Enable in-memory caching (default: true)
    dateFormat: 'YYYY-MM-DD',       // URL date format (default: 'YYYY-MM-DD')
    metadataFormat: 'html-comment', // Metadata format (default: 'html-comment')
    routePrefix: '/blogs',          // URL prefix (default: '/blogs')
    sortOrder: 'desc',              // Sort order: 'desc' or 'asc' (default: 'desc')
    paginate: false,                // Enable pagination (default: false)
    perPage: 10,                    // Items per page if pagination is on (default: 10)
    listTemplate: 'blogs',          // Template name for blog list page (default: 'blogs')
    contentFormat: 'full-html',     // Content format: 'full-html', 'body-only', 'markdown' (default: 'full-html')
    detailTemplate: null,           // Template for blog detail pages (null uses default behavior)
    
    // RSS Feed options
    feedTitle: 'Blog RSS Feed',
    feedDescription: 'Latest posts',
    feedLanguage: 'en'
});

Routes

The blog system creates the following routes:

  • GET /blogs - List all blog posts
  • GET /blogs/:year-:month-:day - View specific blog post
  • GET /blogs/feed.rss - RSS feed
  • GET /blogs/api/posts - JSON API for posts
  • GET /blogs/api/posts/:year-:month-:day - JSON API for specific post

API Methods

// Get all blog posts
const allPosts = blog.getAll();

// Get blog by date
const post = blog.getByDate('2025-08-15');

// Get recent posts
const recentPosts = blog.getRecent(5);

// Get paginated posts
const page = blog.getPaginated(1, 10);

// Search posts
const results = blog.cache.search('keyword');

// Refresh cache manually
blog.refresh();

Templates

The library expects your Express app to have templates in its views directory. You can customize template names with the listTemplate and detailTemplate options.

List Template (blogs.ejs or your custom template name)

<!DOCTYPE html>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>Blog Posts</h1>
    
    <% if (blogs && blogs.length > 0) { %>
        <% blogs.forEach(blog => { %>
            <article>
                <h2><%= blog.title %></h2>
                <time><%= blog.date %></time>
                <p><%= blog.desc %></p>
                <a href="/blogs/<%= blog.year %>-<%= blog.month %>-<%= blog.day %>">Read more</a>
            </article>
        <% }); %>
    <% } else { %>
        <p>No blog posts available.</p>
    <% } %>
    
    <% if (pagination) { %>
        <!-- Add pagination controls here -->
    <% } %>
</body>
</html>

The library passes these variables to your list template:

  • blogs - Array of blog objects
  • pagination - Pagination data (if enabled)
  • search - Current search query
  • version - From app.locals.version

Detail Template (optional)

When using contentFormat other than 'full-html', you can specify a detailTemplate:

<!-- If using a layout system (e.g., express-ejs-layouts), just the content: -->
<article class="blog-post">
    <h1><%= title %></h1>
    <time datetime="<%= date %>"><%= date %></time>
    <div class="content">
        <%- renderedContent %>
    </div>
</article>

<!-- Or if creating a full page template: -->
<!DOCTYPE html>
<html>
<head>
    <title><%= title %> - My Blog</title>
    <link rel="stylesheet" href="/css/blog.css">
</head>
<body>
    <nav><!-- Your navigation --></nav>
    
    <article class="blog-post">
        <h1><%= title %></h1>
        <time datetime="<%= date %>"><%= date %></time>
        <div class="content">
            <%- renderedContent %>
        </div>
    </article>
    
    <footer><!-- Your footer --></footer>
</body>
</html>

Variables passed to detail template:

  • All blog properties at root level (title, date, desc, renderedContent, etc.)
  • blog - Blog object with all metadata including renderedContent
  • pageTitle - Same as blog title, for layout compatibility
  • version - From app.locals.version

Content Format Options

full-html (default)

Traditional approach - blog files are complete HTML documents that are sent directly to the browser. No template required.

body-only

Extracts only the content within <body> tags from HTML files and renders it through your detailTemplate. Perfect for reusing existing HTML content with new layouts.

markdown

Use Markdown files for your blog content. Requires metadataFormat: 'front-matter' for metadata.

const blog = expressStaticBlog({
    blogsDir: './content/blog',
    contentFormat: 'markdown',
    metadataFormat: 'front-matter',
    detailTemplate: 'blog/detail'
});

Example Markdown blog file:

---
date: 2025-01-20
title: My Blog Post
description: This is my blog post about Node.js
---

# Welcome to my blog

This is a paragraph with **bold** text and *italics*.

## Features

- Easy to write
- Clean syntax
- Full markdown support

Front Matter Support

You can use front matter format for metadata with any content format:

const blog = expressStaticBlog({
    blogsDir: './blogs',
    metadataFormat: 'front-matter'
});
---
date: 2025-08-15
title: My Blog Post
description: This is my blog post
tags: javascript, nodejs
---

# My Blog Post

Content goes here...

Advanced Usage

Programmatic Access

// Get all posts sorted by date
const posts = blog.getAll();

// Get posts from a specific year
const yearPosts = blog.cache.getByYear(2025);

// Get posts from a specific month
const monthPosts = blog.cache.getByYearMonth(2025, 8);


// Get cache statistics
const stats = blog.cache.getStats();

Custom Middleware

// Add custom middleware before blog routes
app.use('/blogs', (req, res, next) => {
    // Custom logic here
    next();
}, blog.router());

Migration Guide

Migrating from Static HTML Blog

If you have existing HTML blog files:

  1. Move blog files to a dedicated directory (e.g., ./views/blogs/)
  2. Add metadata to each file using HTML comments:
    <!-- [date: 2025-01-15] -->
    <!-- [desc: Blog post description] -->
  3. Create templates for list and detail pages
  4. Configure the library:
// Before: Manual blog routes
app.get('/blogs', (req, res) => {
    res.render('blog-list', { posts: getPostsSomehow() });
});
app.get('/blogs/:slug', (req, res) => {
    res.render('blog-post', { post: getPostSomehow(req.params.slug) });
});

// After: Using express-simple-static-blog
const blog = expressStaticBlog({
    blogsDir: './views/blogs',
    routePrefix: '/blogs',
    listTemplate: 'blog-list.html',
    contentFormat: 'body-only',     // If your HTML uses includes
    detailTemplate: 'blog-post.html'
});
app.use(blog.router());

Migrating HTML Blogs with EJS Includes

If your blog HTML files use EJS includes (like <%- include('header') %>):

const blog = expressStaticBlog({
    blogsDir: './views/blogs',
    routePrefix: '/blogs',
    listTemplate: 'blogs.html',
    contentFormat: 'body-only',      // IMPORTANT: Extracts body content
    detailTemplate: 'blog-detail.html' // REQUIRED: For processing includes
});

This configuration ensures:

  • Your existing includes continue to work
  • Blog content is wrapped in your site's layout
  • All EJS features remain available

Performance Tips

  1. Caching: Keep caching enabled for production
  2. File Count: The library handles hundreds of blog posts efficiently
  3. Templates: Use custom templates to match your site design
  4. Static Assets: Serve blog images/assets separately

Error Handling

The library includes built-in error handling:

app.use((err, req, res, next) => {
    if (err.status === 404) {
        res.status(404).send('Blog post not found');
    } else {
        res.status(500).send('Server error');
    }
});

Common Issues & Solutions

"Cannot find module 'blogs'" Error

Problem: Express can't find your blog list template.

Solution:

// If using .html files:
listTemplate: 'blogs.html'  // Include the extension

// If using .ejs files:
listTemplate: 'blogs'       // No extension needed

EJS Includes Not Working in Blog Posts

Problem: Your blog posts have <%- include() %> statements that aren't being processed.

Solution: Use the template-based approach:

const blog = expressStaticBlog({
    blogsDir: './views/blogs',
    contentFormat: 'body-only',      // Extract body content
    detailTemplate: 'blog-detail'    // Use a template for rendering
});

"Cannot find module 'html'" Error

Problem: Express doesn't know how to render .html files.

Solution: Configure Express to use EJS for .html files:

app.engine('.html', require('ejs').renderFile);

Blog Posts Show Raw HTML

Problem: You see HTML tags in your blog posts instead of rendered content.

Solution: Use <%- %> instead of <%= %> in your templates:

<%- blog.content %>      <!-- Renders HTML -->
<%= blog.content %>      <!-- Escapes HTML (shows tags) -->

Troubleshooting

Common Issues

Blog posts not showing up?

  • Ensure your HTML files have the correct metadata format
  • Check that the blogsDir path is correct
  • Verify file permissions allow reading

Templates not rendering?

  • Make sure you have a template file matching your listTemplate option
  • If using .html extension, specify it: listTemplate: 'blogs.html'
  • Check that your view engine is properly configured
  • For .html files with EJS: app.engine('.html', require('ejs').renderFile)
  • Ensure EJS is installed as a peer dependency

EJS includes not working in blog posts?

  • Use contentFormat: 'body-only' to extract body content
  • Specify a detailTemplate to process blog content through EJS
  • This allows your blog posts to use your site's layout and includes

Performance issues?

  • Enable caching (it's on by default)
  • Consider paginating if you have many posts
  • Use the getRecent() method for homepage displays

Date parsing errors?

  • Use the ISO format (YYYY-MM-DD) for dates
  • Ensure dates are in the correct metadata format

TypeScript Support

This library can be used in TypeScript projects, though type definitions are not yet included.

Basic Usage in TypeScript

// Add module declaration to your project
declare module 'express-simple-static-blog';

// Import and use
import * as express from 'express';
import * as expressStaticBlog from 'express-simple-static-blog';

const app = express();

const blog = expressStaticBlog({
    blogsDir: './views/blogs',
    routePrefix: '/blogs',
    listTemplate: 'blogs',
    contentFormat: 'markdown',
    detailTemplate: 'blog-detail'
});

app.use(blog.router());

Alternative Approach

If you prefer not to use module declarations:

// Use require with type assertion
const expressStaticBlog = require('express-simple-static-blog') as any;

// Or with a basic type
interface BlogSystem {
    router: () => express.Router;
    getAll: () => any[];
    getByDate: (date: string) => any;
    getRecent: (count: number) => any[];
    refresh: () => void;
}

const blog = expressStaticBlog({
    blogsDir: './views/blogs'
}) as BlogSystem;

Note on IntelliSense

Since type definitions are not yet provided, you won't get IntelliSense autocomplete for configuration options or methods. Refer to this documentation for available options and methods.

Type definitions may be added in a future release. If you need TypeScript support urgently, consider opening an issue or contributing type definitions.

Contributing

Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.

Please make sure to:

  • Update tests as appropriate
  • Follow the existing code style
  • Update documentation if needed

License

MIT

Support

For issues and feature requests, please use the GitHub issue tracker.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published