Comments Endpoints
Comments are nested under posts — their URLs reflect this relationship. This lesson adds the comments router and controller.
Comments router
Section titled “Comments router”import { Router } from 'express'import { listComments, createComment } from '../controllers/comments.js'import { authenticate } from '../middleware/authenticate.js'import { requireFields } from '../middleware/validate.js'
const router = Router({ mergeParams: true }) // inherit :id from parent router
router.get('/:id/comments', listComments)router.post('/:id/comments', authenticate, requireFields('body'), createComment)
export default router{ mergeParams: true } merges the parent router’s params into this router — necessary when nesting routers.
Comments controller
Section titled “Comments controller”import { Request, Response } from 'express'import { db } from '../db/index.js'
interface CommentRow { id: number; body: string; user_id: number post_id: number; created_at: string; author_username: string}
// GET /posts/:id/commentsexport function listComments(req: Request, res: Response) { const postId = parseInt(req.params.id)
// Verify the post exists first const post = db.prepare('SELECT id FROM posts WHERE id = ?').get(postId) if (!post) return res.status(404).json({ error: 'Post not found' })
const comments = db.prepare(` SELECT comments.*, users.username AS author_username FROM comments JOIN users ON comments.user_id = users.id WHERE comments.post_id = ? ORDER BY comments.created_at ASC `).all(postId) as CommentRow[]
res.json(comments)}
// POST /posts/:id/commentsexport function createComment(req: Request, res: Response) { const postId = parseInt(req.params.id) const { body } = req.body as { body: string } const { userId } = res.locals.user
// Verify the post exists const post = db.prepare('SELECT id FROM posts WHERE id = ?').get(postId) if (!post) return res.status(404).json({ error: 'Post not found' })
const result = db.prepare( 'INSERT INTO comments (body, user_id, post_id) VALUES (?, ?, ?)' ).run(body.trim(), userId, postId)
const comment = db.prepare(` SELECT comments.*, users.username AS author_username FROM comments JOIN users ON comments.user_id = users.id WHERE comments.id = ? `).get(result.lastInsertRowid) as CommentRow
res.status(201).json(comment)}Mounting the comments router
Section titled “Mounting the comments router”import commentsRouter from './routes/comments.js'
app.use('/posts', commentsRouter)// Results in: GET /posts/:id/comments and POST /posts/:id/commentsUser profiles endpoint
Section titled “User profiles endpoint”While we’re at it, add a basic user profile endpoint:
import { Router } from 'express'import { db } from '../db/index.js'
const router = Router()
router.get('/:id', (req, res) => { const userId = parseInt(req.params.id)
const user = db.prepare( 'SELECT id, username, bio, created_at FROM users WHERE id = ?' ).get(userId) as { id: number; username: string; bio: string | null; created_at: string } | undefined
if (!user) return res.status(404).json({ error: 'User not found' })
const { total: postCount } = db.prepare( 'SELECT COUNT(*) as total FROM posts WHERE user_id = ?' ).get(userId) as { total: number }
res.json({ ...user, postCount })})
export default routerTesting comments
Section titled “Testing comments”# List commentscurl http://localhost:3000/posts/1/comments
# Add a commentcurl -X POST http://localhost:3000/posts/1/comments \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"body": "Great post!"}'Exercise
Section titled “Exercise”- Create
src/routes/comments.tsandsrc/controllers/comments.ts. - Mount both routers in
src/index.ts. - Create a post, then add two comments, then list them.
- Try adding a comment to a non-existent post — confirm 404.
- Comments are nested under posts:
GET /posts/:id/comments,POST /posts/:id/comments. - Use
{ mergeParams: true }when the nested router needs the parent’s route params. - Always verify the parent resource (post) exists before operating on its children (comments).
ORDER BY created_at ASCfor comments — show oldest first (chronological thread order).