Skip to content

Instantly share code, notes, and snippets.

@Anonyfox
Last active April 29, 2018 23:39
Show Gist options
  • Save Anonyfox/32f4b599008b97500466 to your computer and use it in GitHub Desktop.
Save Anonyfox/32f4b599008b97500466 to your computer and use it in GitHub Desktop.
nodejs file server example

Influenza, the minimalistic blogging engine.

Based on Node.js/Express.js and flat markdown files. This is the main (and only) code file, containing all the logic and code, and is written in literate Coffeescript. The Idea is that you just put your articles as markdown files (*.md) in the /posts folder, and Influenza takes care of all the other stuff.

Only the following npm modules are used:

express   = require "express"
exphbs    = require "express-handlebars"
mde       = require "markdown-extra"
markdown  = require("markdown").markdown
_         = require "underscore"
async     = require "async"
rss       = require "rss"
fs        = require "fs"
app       = express()

Since this is an Express.js 4.X web app, these are the relevant application settings, both for development and production environment:

app.engine '.hbs', exphbs(defaultLayout: 'postLayout', extname: '.hbs', partialsDir: 'views/partials')
app.set 'view engine', '.hbs'
app.set 'views', "#{__dirname}/views"
app.enable 'trust proxy'
# app.use express.compress()
app.use express.static "#{__dirname}/public"
# app.use express.responseTime()

Get all posts

This function will read all the files from the /posts directory. It may be better to cache the results, but to prevent restart-needs everytime a post gets updated, it is preferred to read from scratch everytime. Should be fast enough for a simple blog.

readPosts = (fn) ->
  fs.readdir 'posts', (error, files) ->
    filePaths = _.map files, (f) -> "posts/#{f}"
    async.map filePaths, fs.stat, (error, fileStats) ->
      posts = ( for file, index in files
        title: file.split(".")[0]
        slug: file.split(".")[0].replace(/\W+/g,"-")
        createdAt: fileStats[index].ctime
      )
      fn null, _.sortBy posts, 'createdAt'

Fetch a single Post by Name

This function will transform a given post name into the fully parsed result object (if it exists, of course!).

fetchPost = (name, fn) ->
  filePath = "#{__dirname}/posts/#{name}.md"
  fs.stat filePath, (error, stats) ->
    unless stats
      fn null, null
    else # the file exists
      fs.readFile filePath, {encoding: "utf-8"}, (error, data) ->
        post =
          title: mde.heading data
          content: markdown.toHTML( mde.content data )
          metadata: mde.metadata data
          createdAt: stats.ctime
          updatedAt: stats.mtime
        fn null, post

The index homepage

app.get '/', (req, res) ->
  readPosts (error, posts) ->
    fullUrl = "http://blog.plague-dev.de" + req.originalUrl
    res.render 'home',
      title: "Plague Developer Blog"
      posts: posts
      layout: "mainLayout"
      shareMailSubject: encodeURIComponent "Plague Developer Blog."
      shareMailContent: encodeURIComponent "#{fullUrl}"
      shareUrl: fullUrl
      shareTwitterContent: encodeURIComponent "Plague Developer Blog #{fullUrl}"
      shareLinkedinContent: encodeURIComponent "Plague Developer Blog"
      shareLinkedinUrl: encodeURIComponent fullUrl
      sharePinterestImage: ""

single article view

app.get '/posts/:slug', (req, res) ->
  securedSlug = (req.params.slug or "").replace(/[^\w-]/g, "").replace(/-/g," ")
  fullUrl = "http://blog.plague-dev.de" + req.originalUrl
  fetchPost securedSlug, (error, post) ->
    if error or not post
      res.redirect "/" unless post
    else
      data = _.extend (post || {}),
        layout: "postLayout"
        shareMailSubject: encodeURIComponent "I just found this Article, sounds interesting: #{post.title}"
        shareMailContent: encodeURIComponent "#{fullUrl}"
        shareUrl: fullUrl
        shareTwitterContent: encodeURIComponent "#{post.title} #{fullUrl}"
        shareLinkedinContent: encodeURIComponent post.title
        shareLinkedinUrl: encodeURIComponent fullUrl
        image: (post.metadata.image or "")
        sharePinterestImage: encodeURIComponent(post.metadata.image or "")
      res.render 'post', data

RSS feed with latest articles

app.get '/feed', (req, res) ->
  readPosts (error, posts) ->
    feed = new rss
      title: "Plague Developer Blog"
      description: "Latest thoughts and buzz about tech, startup culture, software and growth hacking."
      feed_url: "http://blog.plague-dev.de/feed"
      site_url: "http://blog.plague-dev.de"
      author: "Maximilian Stroh (@Hisako1337)"
      language: "en_US"
    for post in posts
      item = {}
      item.title = post.title
      item.description = "Click to read!"
      item.url = "http://blog.plague-dev.de/posts/#{post.slug}"
      item.author = "Maximilian Stroh (@Hisako1337)"
      item.date = new Date(post.createdAt)
      feed.item item
    xml = feed.xml "  "
    res.header('Content-Type','application/rss+xml').send(xml)

overview latest articles and stuff

app.get '/posts', (req, res) ->
  readPosts (error, posts) ->
    fullUrl = "http://blog.plague-dev.de" + req.originalUrl
    res.render 'posts',
      title: "All Posts | Plague Developer Blog"
      posts: posts
      layout: "mainLayout"
      shareMailSubject: encodeURIComponent "All Posts | Plague Developer Blog"
      shareMailContent: encodeURIComponent "#{fullUrl}"
      shareUrl: fullUrl
      shareTwitterContent: encodeURIComponent "All Posts | Plague Developer Blog #{fullUrl}"
      shareLinkedinContent: encodeURIComponent "All Posts | Plague Developer Blog"
      shareLinkedinUrl: encodeURIComponent fullUrl
      sharePinterestImage: ""

Catch all nonexistant routes

app.get "*", (req, res) ->
  res.redirect "/"

Server Settings and Port

server = app.listen 2368
console.log "Listening on port 2368"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment