A modular comment widget with voting for static or dynamic websites.
<div id="moontalk-container"></div>
<script type="module">
import MoonTalk from 'https://comment.moonlab.top/moontalk.js';
const widget = new MoonTalk({
server: 'https://comment.moonlab.top',
postId: 'my-post-key',
element: '#moontalk-container',
theme: 'dark',
siteName: 'example.com',
latestCommentsLimit: 5,
});
widget.mount();
</script>Create and mount widget:
const widget = new MoonTalk({
server: 'https://comment.moonlab.top',
postId: 'my-post-key',
element: '#moontalk-container',
theme: 'auto', // 'auto' | 'light' | 'dark'
siteName: 'example.com',
latestCommentsLimit: 5,
pageSize: 10,
});
await widget.mount();POST /api/v2/posts/:postId/comments- Body:
{
"username": "alice",
"content": "hello world",
"email": "[email protected]",
"website": "https://example.com",
"parent_id": 1,
"reply_to": 2
}GET /api/v2/posts/:postId/comments?page=1&limit=10- Response envelope:
{
"data": [
{
"id": 1,
"postId": "my-post-key",
"parentId": null,
"replyTo": null,
"username": "alice",
"content": "hello",
"createdAt": "2026-01-01T00:00:00.000Z",
"hasChildren": true,
"children": []
}
],
"meta": {
"page": 1,
"limit": 10,
"totalPages": 1,
"totalRoots": 1,
"totalComments": 1
}
}GET /api/v2/comments/latest?site=example.com&limit=5
GET /rss/comments.xml?site=example.com&limit=20- Returns RSS 2.0 XML with latest comments for a site (or all sites when
siteis omitted). limitrange:1-20(default5).
GET /api/v2/posts/:postId/votes
PUT /api/v2/posts/:postId/vote- Body:
{ "value": -1 | 0 | 1 }
Success:
{ "data": {}, "meta": {} }Error:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human readable error",
"details": {}
}
}Comments are plain text only. HTML is not rendered.
Run:
/Users/lingc/Projects/nodejs/MoonTalk/db/migrations/001_v2_optimizations.sql
It adds:
- Unique index for votes on
(post_id, ip) - Vote value check constraint
- Query indexes for comments and latest comments
Install dependencies and run server:
pnpm install
pnpm startRun tests:
pnpm test