There’s something useful about a forum thread that’s still answering people’s questions five years after someone started it.
The original problem is there. So are the suggestions, the things that didn’t work, and the reply halfway down that actually solved it. Someone arriving later can follow the whole conversation without needing to know who to ask.
I think that’s still worth building for.
That’s the idea behind Meith, an open-source, self-hosted forum platform I’m working on. It gives a community its own board, on its own domain, with a PostgreSQL database it controls. Threads, search, moderation, private messages and subscriptions are all part of that foundation.
But this is DEV, so let’s get into how it’s built, and why.
The stack is TypeScript, Next.js, React and PostgreSQL, with Drizzle handling the database layer. The repository is a pnpm workspace containing the board application, the project website, a worker, an operator CLI, domain packages, themes and plugins.
The architecture is essentially a modular monolith. Features such as accounts, threads, posts and moderation live in separate packages, with explicit boundaries between the forum’s business rules and the infrastructure running them.
That separation matters quite a bit in forum software.
Creating a thread involves more than inserting some text. You need to know whether the member can post in that forum, whether the content needs approval, which counters should change, and what should happen afterwards. Those rules need a consistent home as the application grows.
In Meith, domain packages don’t import Next.js, React or database drivers. They work through repository interfaces. A runtime package connects those services to their concrete implementations.
The request flow looks roughly like this:
Route or server action
→ validate input and authorise the request
→ call a domain service
→ persist through a repository implementation
→ prepare the public view model
→ invalidate affected caches and respond
Next.js handles the web-facing parts: requests, rendering, redirects and cache invalidation. The domain packages handle the forum behaviour.
That means the business logic can be tested without starting a Next.js application, and services can be composed for the worker and CLI too. Dependency checks enforce those import boundaries, which helps keep the architecture intact as new features land.
The rendering approach follows a similar idea: put the work where it belongs.
Pages and layouts use React Server Components, with smaller client components where interaction needs them. Most reading and posting works with JavaScript disabled. Forms use native submission, with JavaScript adding pending states and richer feedback.
I like this approach for a forum because so much of the experience is straightforward: read a page, follow a link, write a reply. Those actions deserve a dependable baseline.
It also keeps the server authoritative. Every action checks its own access. Hiding a button or protecting a layout doesn’t grant the underlying operation permission to trust the request.
Permissions carry through to the data being rendered, too. Components and themes receive explicit view models containing the fields they need. Raw database rows stay out of the presentation layer.
Caching needs the same care. A forum can have public discussions, member-only sections and private moderation areas on the same installation. A result filtered for one member cannot safely become a global cache entry. Meith separates actor-independent caching from results that depend on who is asking.
These are fairly ordinary concerns individually. Together, they’re a large part of what makes a forum reliable.
PostgreSQL does a substantial amount of the infrastructure work. Alongside the content, it holds sessions, full-text search data, queues and scheduled task state. A single-instance board can use that foundation without requiring a separate search service or message broker. Redis is available for shared caching when running multiple web instances.
There’s an operational trade-off there: the database has more responsibilities, and its queries and workload still need attention. But keeping the initial deployment understandable is valuable when the person maintaining the community is also the person answering support questions and organising events.
Background work uses a transactional outbox.
When a content operation needs to emit an event, the content change and its outbox record are written in the same database transaction. That closes the gap where a post could be saved successfully and the process could crash before recording the follow-up work.
Processing happens afterwards. The delivery model assumes events can arrive more than once, so handlers need to tolerate retries. Database-backed queue claims and task leases coordinate background processing.
Depending on the deployment, scheduled work runs through worker loops or an authenticated HTTP tick endpoint. Notifications, indexing and other maintenance work need that scheduler to keep running; it’s part of operating the board.
Customisation has its own boundaries.
A theme supplies React components for named slots and receives prepared models and rendered regions from the application. It can inherit an existing theme and override the pieces it needs.
Themes don’t query the database, decide permissions or implement mutations. Forms remain owned by the application and are passed into the presentation.
That makes a visual redesign a much more contained job. Changing how a post looks shouldn’t require understanding how posting permissions are calculated.
Plugins handle additional behaviour through declared hooks, routes, pages, settings, storage and scheduled tasks. They run inside the board process, so they’re trusted server code. The extension contracts provide structure; they aren’t a sandbox for arbitrary code.
There’s also a REST API and webhooks for connecting other applications. API tokens narrow the permissions of their owner: a scope doesn’t give an integration access to content the underlying account couldn’t access.
For a developer considering Meith, those are the reasons I’d look beyond the screenshots.
You get a working forum foundation, a familiar TypeScript and React stack, and defined places to extend it. You can change the presentation without taking ownership of the authorisation logic. You can add behaviour through plugins and connect other tools through the API.
For the community, the benefit is more direct: its discussions live on infrastructure it controls.
Meith is MIT licensed, with no licence fee or per-member charge. Hosting still costs money, and self-hosting comes with backups, updates, email delivery and monitoring. That responsibility is part of the choice.
If you want to explore it locally, you can start with Node.js 22 or newer:
npx create-meith my-board
cd my-board
npm install
npm run dev
That opens a preview using sample data. It doesn’t save posts or create accounts; connecting PostgreSQL is the next step for a writable board.
You can find the source on GitHub, browse the documentation, or try the demo.
I’m interested in what developers want from forum software now, particularly the people who end up maintaining it. What would make you comfortable choosing a forum platform for a community you expect to still be running years from now?
Top comments (0)