Skip to main content

Mark Notification as Read API

Overview​

The Mark Notification as Read API allows authenticated users to mark a specific notification as read. This endpoint updates the notification status in the database and helps manage user notification states.

Endpoint Details​

  • URL: /api/notifications/[id]/read
  • Method: POST
  • Authentication: Required (NextAuth.js session)
  • Content-Type: application/json

URL Parameters​

ParameterTypeRequiredDescription
idstringYesThe unique identifier of the notification to mark as read

Authentication​

This endpoint requires user authentication via NextAuth.js session:

import { getSession } from 'next-auth/react'

const session = await getSession()
if (!session) {
// Handle unauthenticated state
}

Request Body​

This endpoint does not require a request body. The notification ID is provided in the URL path.

Response Format​

Success Response (200)​

{
"success": true,
"message": "Notification marked as read"
}

Error Responses​

Unauthorized (401)​

{
"error": "User not authenticated"
}

Bad Request (400)​

{
"error": "Notification ID is required"
}

Forbidden (403)​

{
"error": "Unauthorized access to notification"
}

Not Found (404)​

{
"error": "Notification not found"
}

Internal Server Error (500)​

{
"error": "Failed to mark notification as read. Please try again later."
}

Implementation Examples​

React Component with Individual Notification​

import { useState } from 'react'
import { useSession } from 'next-auth/react'

interface Notification {
id: string
title: string
message: string
isRead: boolean
createdAt: string
type: 'info' | 'warning' | 'success' | 'error'
}

function NotificationItem({ notification, onMarkAsRead }: {
notification: Notification
onMarkAsRead: (id: string) => void
}) {
const { data: session } = useSession()
const [loading, setLoading] = useState(false)

const handleMarkAsRead = async () => {
if (!session || notification.isRead) return

setLoading(true)

try {
const response = await fetch(`/api/notifications/${notification.id}/read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})

if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Failed to mark as read')
}

onMarkAsRead(notification.id)
} catch (error) {
console.error('Error marking notification as read:', error)
} finally {
setLoading(false)
}
}

return (
<div className={`p-4 border rounded-lg ${
notification.isRead ? 'bg-gray-50' : 'bg-white border-blue-200'
}`}>
<div className="flex items-start justify-between">
<div className="flex-1">
<h4 className={`font-semibold ${
notification.isRead ? 'text-gray-600' : 'text-gray-900'
}`}>
{notification.title}
</h4>
<p className={`mt-1 text-sm ${
notification.isRead ? 'text-gray-500' : 'text-gray-700'
}`}>
{notification.message}
</p>
<span className="text-xs text-gray-400 mt-2 block">
{new Date(notification.createdAt).toLocaleDateString()}
</span>
</div>

{!notification.isRead && (
<button
onClick={handleMarkAsRead}
disabled={loading}
className="ml-4 px-3 py-1 text-sm bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? 'Marking...' : 'Mark as Read'}
</button>
)}
</div>

{!notification.isRead && (
<div className="w-2 h-2 bg-blue-500 rounded-full absolute top-4 right-4"></div>
)}
</div>
)
}

React Hook for Notification Management​

import { useState, useCallback } from 'react'
import { useSession } from 'next-auth/react'

export function useNotifications() {
const { data: session } = useSession()
const [notifications, setNotifications] = useState<Notification[]>([])
const [loading, setLoading] = useState(false)

const markAsRead = useCallback(async (notificationId: string) => {
if (!session) return

try {
const response = await fetch(`/api/notifications/${notificationId}/read`, {
method: 'POST',
})

if (!response.ok) {
throw new Error('Failed to mark notification as read')
}

// Update local state
setNotifications(prev =>
prev.map(notification =>
notification.id === notificationId
? { ...notification, isRead: true }
: notification
)
)

return true
} catch (error) {
console.error('Error marking notification as read:', error)
return false
}
}, [session])

const markAllAsRead = useCallback(async () => {
if (!session) return

setLoading(true)
try {
const response = await fetch('/api/notifications/read-all', {
method: 'POST',
})

if (!response.ok) {
throw new Error('Failed to mark all notifications as read')
}

// Update local state
setNotifications(prev =>
prev.map(notification => ({ ...notification, isRead: true }))
)
} catch (error) {
console.error('Error marking all notifications as read:', error)
} finally {
setLoading(false)
}
}, [session])

return {
notifications,
setNotifications,
markAsRead,
markAllAsRead,
loading
}
}

Server Action (Next.js 13+)​

'use server'

import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'

export async function markNotificationAsReadAction(notificationId: string) {
const session = await auth()

if (!session) {
redirect('/auth/signin')
}

try {
const response = await fetch(`${process.env.NEXTAUTH_URL}/api/notifications/${notificationId}/read`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cookie': request.headers.get('cookie') || '',
},
})

if (!response.ok) {
const error = await response.json()
throw new Error(error.error || 'Failed to mark notification as read')
}

// Revalidate the notifications page
revalidatePath('/notifications')

} catch (error) {
console.error('Server action error:', error)
throw error
}
}

cURL Example​

curl -X POST https://ring.ck.ua/api/notifications/notification123/read \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=your-session-token"

Business Logic​

The endpoint performs the following operations:

  1. Authentication Check: Verifies user session and extracts user ID
  2. Parameter Validation: Ensures notification ID is provided in URL
  3. Authorization Check: Verifies user has access to the notification
  4. Database Update: Marks the notification as read in Firestore
  5. Response: Returns success confirmation

Database Schema​

Notification Document Structure​

{
"id": "notification123",
"userId": "user456",
"title": "New message received",
"message": "You have a new message from John Doe",
"type": "info",
"isRead": false,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z",
"metadata": {
"sourceId": "message789",
"sourceType": "message",
"actionUrl": "/messages/789"
}
}

After Marking as Read​

{
"id": "notification123",
"userId": "user456",
"title": "New message received",
"message": "You have a new message from John Doe",
"type": "info",
"isRead": true,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T14:22:00Z",
"readAt": "2024-01-15T14:22:00Z",
"metadata": {
"sourceId": "message789",
"sourceType": "message",
"actionUrl": "/messages/789"
}
}

Security Considerations​

  • Authentication Required: Only authenticated users can mark notifications as read
  • Authorization Check: Users can only mark their own notifications as read
  • Input Validation: Notification ID is validated for format and existence
  • Error Handling: Sensitive information is not exposed in error messages

Performance Notes​

  • Single Database Query: Efficiently updates one notification record
  • No Caching: Real-time updates ensure immediate consistency
  • Optimistic Updates: Frontend can update UI before server confirmation

Rate Limiting​

  • User-based: 30 requests per minute per authenticated user
  • Global: 1000 requests per minute across all users

Error Handling​

The API implements comprehensive error handling:

  • Missing notification ID returns 400 status
  • Authentication errors return 401 status
  • Authorization errors return 403 status
  • Missing notifications return 404 status
  • Server errors return 500 status with user-friendly messages

Integration with Real-time Updates​

// WebSocket integration for real-time notification updates
import { useEffect } from 'react'
import { useWebSocket } from '@/hooks/useWebSocket'

function NotificationSystem() {
const { socket } = useWebSocket()
const { notifications, setNotifications, markAsRead } = useNotifications()

useEffect(() => {
if (!socket) return

// Listen for real-time notification updates
socket.on('notification:read', (data: { notificationId: string }) => {
setNotifications(prev =>
prev.map(notification =>
notification.id === data.notificationId
? { ...notification, isRead: true, readAt: new Date().toISOString() }
: notification
)
)
})

return () => {
socket.off('notification:read')
}
}, [socket, setNotifications])

return (
<div className="notification-system">
{/* Notification components */}
</div>
)
}

Testing​

Unit Test Example​

import { POST } from '@/app/api/notifications/[id]/read/route'
import { NextRequest } from 'next/server'

// Mock authentication and services
jest.mock('@/auth')
jest.mock('@/services/notifications/notification-service')

describe('/api/notifications/[id]/read', () => {
it('should mark notification as read successfully', async () => {
// Mock authenticated session
(getServerAuthSession as jest.Mock).mockResolvedValue({
user: { id: 'user123', role: 'member' }
})

const request = new NextRequest('http://localhost:3000/api/notifications/notif123/read', {
method: 'POST'
})

const response = await POST(request, { params: { id: 'notif123' } })
const data = await response.json()

expect(response.status).toBe(200)
expect(data.success).toBe(true)
expect(data.message).toBe('Notification marked as read')
})

it('should return 404 for non-existent notification', async () => {
(markNotificationAsRead as jest.Mock).mockRejectedValue(
new Error('Notification not found')
)

const request = new NextRequest('http://localhost:3000/api/notifications/invalid/read', {
method: 'POST'
})

const response = await POST(request, { params: { id: 'invalid' } })
const data = await response.json()

expect(response.status).toBe(404)
expect(data.error).toBe('Notification not found')
})
})