Published at: 2022-06-25
tags: #nodejs
Moving the blog to nodejs
So I had a few extra hours to kill the other day and decided to move the blog from being a pure PHP creation to a pure nodejs thing. The main reason is I felt that using laravel just to host a few static pages was overkill. So I decided to use Node where having just 4 direct dependencies means I have over 1gb in node_modules
!
Well the main reason is I really don't use PHP anymore at all since I've been working with node for the past two years. So having something smallish written in an ecosystem I use daily is easier to maintain and improve. I do hope to move this blog to a real programming language when I get around to learning it. I've been eyeing rust for the past two years but been feeling rather lazy.
Anyways this post if to detail how the blog was moved over.
It was rather simple since everything is in markdown documents so I just had to move my content folder over and have something read the files into memory. This is the code that does that. Its pretty sloppy since I might have done this in like 10min.
import { readdir, readFile } from "fs/promises";
import path from "path";
import showdown from "showdown";
import { filterByPublishedStatus, sortByDate } from "./postFilters";
const converter = new showdown.Converter({ metadata: true });
export interface IPost {
html: string;
metadata: {
slug: string;
title: string;
date: string;
published: string;
tags: string | string[];
};
}
export let posts: IPost[] = [];
export const posts_map = new Map<string, IPost>();
export const loadPosts = async () => {
const dir_name = path.join(__dirname, "../content");
const data = await readdir(dir_name);
const local_posts = data.map(async (file): Promise<IPost> => {
const html = converter.makeHtml(
await readFile(path.join(dir_name, file), "utf-8")
);
const metadata = converter.getMetadata() as IPost["metadata"];
metadata.tags =
metadata?.tags && typeof metadata?.tags === "string"
? metadata.tags.split(",").map((item) => item.trim())
: [];
return { html, metadata };
});
posts = await Promise.all(local_posts);
posts = sortByDate(posts);
posts.forEach((post) => {
posts_map.set(post.metadata.slug, post);
});
posts = filterByPublishedStatus(posts);
};
Then there is three express routes that are used across the blog.
One is to serve the static content when it exists.
app.use(express.static("public"));
The other two are just the front page and one to load to each individual post.
There is a few extra things like a daily quote I added from a random api just for fun.
app.get("/posts/:slug", (req, res) => {
const slug = req.params.slug;
const post = posts_map.get(slug);
res.render("post", { post });
});
app.get("/", (req, res) => {
const tag = req.query.tag as string;
res.render("home", {
posts: filterByTag(posts, tag),
posts_map,
daily_quote,
});
});
We are using handlebars for the templating because while Remix is nice and all I didn't feel like using something so heavy to serve up basically static content. The core of the templates are:
// The home page
<section class="my-3">
{{{daily_quote}}}
</section>
<h2>Blog Posts</h2>
<ul>
{{#each posts}}
<li>
{{toDate this.metadata.date}}
-
<a href="/posts/{{this.metadata.slug}}">{{this.metadata.title}}</a>
{{#each this.metadata.tags}}
tags:
<a href="?tag={{this}}">#{{this}}</a>
{{/each}}
</li>
{{/each}}
</ul>
// The post page
{{>menu}}
<a href="/">Back Home</a>
<article class="my-5">
<p>Published at: {{toDate post.metadata.date}}</p>
<p>
{{#each post.metadata.tags}}
tags:
<a href="/?tag={{this}}">#{{this}}</a>
{{/each}}
</p>
<h2 class="mb-3">{{post.metadata.title}}</h2>
{{{post.html}}}
</article>
Thats about it. We hold all the posts in memory because its faster that way. I could technically have all of them turned into static pages before deploy but thats more work that is not really needed.
Currently I am going to host this in digital ocean's app engine thing and see how it goes. Currently getting everything loaded in 100ms so better than my last time messing around with it where a simple node api was taking 200ms. If you do it in a droplet with nginx the response time is around 50ms so eh.
I'll keep adding to this blog if I ever get around to using it more. Anyways I hope you enjoyed this mess of a post.